[CalendarServer-changes] [5150] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Wed Feb 17 16:11:23 PST 2010


Revision: 5150
          http://trac.macosforge.org/projects/calendarserver/changeset/5150
Author:   glyph at apple.com
Date:     2010-02-17 16:11:20 -0800 (Wed, 17 Feb 2010)
Log Message:
-----------
Merge in use-system-twisted branch, which upgrades to the stock, unpatched 9.0.0 release of Twisted.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/provision/root.py
    CalendarServer/trunk/calendarserver/provision/test/test_root.py
    CalendarServer/trunk/calendarserver/sidecar/task.py
    CalendarServer/trunk/calendarserver/tap/caldav.py
    CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
    CalendarServer/trunk/calendarserver/tools/gateway.py
    CalendarServer/trunk/calendarserver/tools/principals.py
    CalendarServer/trunk/calendarserver/tools/test/test_gateway.py
    CalendarServer/trunk/calendarserver/tools/test/test_principals.py
    CalendarServer/trunk/calendarserver/util.py
    CalendarServer/trunk/calendarserver/webadmin/resource.py
    CalendarServer/trunk/calendarserver/webcal/resource.py
    CalendarServer/trunk/setup.py
    CalendarServer/trunk/support/build.sh
    CalendarServer/trunk/twext/__init__.py
    CalendarServer/trunk/twext/web2/__init__.py
    CalendarServer/trunk/twext/web2/channel/__init__.py
    CalendarServer/trunk/twext/web2/channel/http.py
    CalendarServer/trunk/twext/web2/dav/__init__.py
    CalendarServer/trunk/twext/web2/dav/davxml.py
    CalendarServer/trunk/twext/web2/dav/test/__init__.py
    CalendarServer/trunk/twext/web2/dav/test/test_davxml.py
    CalendarServer/trunk/twext/web2/http.py
    CalendarServer/trunk/twistedcaldav/__init__.py
    CalendarServer/trunk/twistedcaldav/accesslog.py
    CalendarServer/trunk/twistedcaldav/accounting.py
    CalendarServer/trunk/twistedcaldav/authkerb.py
    CalendarServer/trunk/twistedcaldav/cache.py
    CalendarServer/trunk/twistedcaldav/caldavxml.py
    CalendarServer/trunk/twistedcaldav/carddavxml.py
    CalendarServer/trunk/twistedcaldav/client/pool.py
    CalendarServer/trunk/twistedcaldav/client/reverseproxy.py
    CalendarServer/trunk/twistedcaldav/customxml.py
    CalendarServer/trunk/twistedcaldav/directory/addressbook.py
    CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py
    CalendarServer/trunk/twistedcaldav/directory/calendar.py
    CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
    CalendarServer/trunk/twistedcaldav/directory/digest.py
    CalendarServer/trunk/twistedcaldav/directory/directory.py
    CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
    CalendarServer/trunk/twistedcaldav/directory/principal.py
    CalendarServer/trunk/twistedcaldav/directory/sudo.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_augment.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_digest.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_guidchange.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_modify.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_sudo.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py
    CalendarServer/trunk/twistedcaldav/directory/test/util.py
    CalendarServer/trunk/twistedcaldav/directory/util.py
    CalendarServer/trunk/twistedcaldav/directory/wiki.py
    CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py
    CalendarServer/trunk/twistedcaldav/directory/xmlfile.py
    CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py
    CalendarServer/trunk/twistedcaldav/dropbox.py
    CalendarServer/trunk/twistedcaldav/extensions.py
    CalendarServer/trunk/twistedcaldav/fileops.py
    CalendarServer/trunk/twistedcaldav/freebusyurl.py
    CalendarServer/trunk/twistedcaldav/ical.py
    CalendarServer/trunk/twistedcaldav/icaldav.py
    CalendarServer/trunk/twistedcaldav/mail.py
    CalendarServer/trunk/twistedcaldav/memcacheprops.py
    CalendarServer/trunk/twistedcaldav/method/copymove.py
    CalendarServer/trunk/twistedcaldav/method/copymove_contact.py
    CalendarServer/trunk/twistedcaldav/method/delete.py
    CalendarServer/trunk/twistedcaldav/method/delete_common.py
    CalendarServer/trunk/twistedcaldav/method/get.py
    CalendarServer/trunk/twistedcaldav/method/mkcalendar.py
    CalendarServer/trunk/twistedcaldav/method/mkcol.py
    CalendarServer/trunk/twistedcaldav/method/propfind.py
    CalendarServer/trunk/twistedcaldav/method/put.py
    CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
    CalendarServer/trunk/twistedcaldav/method/put_common.py
    CalendarServer/trunk/twistedcaldav/method/report.py
    CalendarServer/trunk/twistedcaldav/method/report_addressbook_multiget.py
    CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py
    CalendarServer/trunk/twistedcaldav/method/report_calquery.py
    CalendarServer/trunk/twistedcaldav/method/report_common.py
    CalendarServer/trunk/twistedcaldav/method/report_freebusy.py
    CalendarServer/trunk/twistedcaldav/method/report_multiget.py
    CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py
    CalendarServer/trunk/twistedcaldav/mkcolxml.py
    CalendarServer/trunk/twistedcaldav/pdmonster.py
    CalendarServer/trunk/twistedcaldav/report_addressbook_findshared.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/schedule.py
    CalendarServer/trunk/twistedcaldav/scheduling/caldav.py
    CalendarServer/trunk/twistedcaldav/scheduling/imip.py
    CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
    CalendarServer/trunk/twistedcaldav/scheduling/ischedule.py
    CalendarServer/trunk/twistedcaldav/scheduling/ischeduleservers.py
    CalendarServer/trunk/twistedcaldav/scheduling/processing.py
    CalendarServer/trunk/twistedcaldav/scheduling/scheduler.py
    CalendarServer/trunk/twistedcaldav/scheduling/test/test_imip.py
    CalendarServer/trunk/twistedcaldav/scheduling/test/test_implicit.py
    CalendarServer/trunk/twistedcaldav/static.py
    CalendarServer/trunk/twistedcaldav/stdconfig.py
    CalendarServer/trunk/twistedcaldav/test/test_DAV.py
    CalendarServer/trunk/twistedcaldav/test/test_accounting.py
    CalendarServer/trunk/twistedcaldav/test/test_cache.py
    CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py
    CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py
    CalendarServer/trunk/twistedcaldav/test/test_extensions.py
    CalendarServer/trunk/twistedcaldav/test/test_freebusyquery.py
    CalendarServer/trunk/twistedcaldav/test/test_kerberos.py
    CalendarServer/trunk/twistedcaldav/test/test_log.py
    CalendarServer/trunk/twistedcaldav/test/test_memcacheprops.py
    CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py
    CalendarServer/trunk/twistedcaldav/test/test_multiget.py
    CalendarServer/trunk/twistedcaldav/test/test_options.py
    CalendarServer/trunk/twistedcaldav/test/test_props.py
    CalendarServer/trunk/twistedcaldav/test/test_schedule.py
    CalendarServer/trunk/twistedcaldav/test/test_stdconfig.py
    CalendarServer/trunk/twistedcaldav/test/test_upgrade.py
    CalendarServer/trunk/twistedcaldav/test/test_validation.py
    CalendarServer/trunk/twistedcaldav/test/util.py
    CalendarServer/trunk/twistedcaldav/timezoneservice.py
    CalendarServer/trunk/twistedcaldav/upgrade.py
    CalendarServer/trunk/twistedcaldav/vcard.py
    CalendarServer/trunk/txcaldav/calendarstore/file.py
    CalendarServer/trunk/txcaldav/calendarstore/test/test_file.py
    CalendarServer/trunk/txdav/propertystore/test/test_xattr.py
    CalendarServer/trunk/txdav/propertystore/xattr.py

Added Paths:
-----------
    CalendarServer/trunk/twext/patches.py
    CalendarServer/trunk/twext/python/filepath.py
    CalendarServer/trunk/twext/web2/_version.py
    CalendarServer/trunk/twext/web2/auth/
    CalendarServer/trunk/twext/web2/auth/__init__.py
    CalendarServer/trunk/twext/web2/auth/basic.py
    CalendarServer/trunk/twext/web2/auth/digest.py
    CalendarServer/trunk/twext/web2/auth/interfaces.py
    CalendarServer/trunk/twext/web2/auth/wrapper.py
    CalendarServer/trunk/twext/web2/client/
    CalendarServer/trunk/twext/web2/client/__init__.py
    CalendarServer/trunk/twext/web2/client/http.py
    CalendarServer/trunk/twext/web2/client/interfaces.py
    CalendarServer/trunk/twext/web2/compat.py
    CalendarServer/trunk/twext/web2/dav/_errorbase.py
    CalendarServer/trunk/twext/web2/dav/auth.py
    CalendarServer/trunk/twext/web2/dav/element/
    CalendarServer/trunk/twext/web2/dav/element/__init__.py
    CalendarServer/trunk/twext/web2/dav/element/base.py
    CalendarServer/trunk/twext/web2/dav/element/extensions.py
    CalendarServer/trunk/twext/web2/dav/element/parser.py
    CalendarServer/trunk/twext/web2/dav/element/rfc2518.py
    CalendarServer/trunk/twext/web2/dav/element/rfc3253.py
    CalendarServer/trunk/twext/web2/dav/element/rfc3744.py
    CalendarServer/trunk/twext/web2/dav/element/rfc4331.py
    CalendarServer/trunk/twext/web2/dav/element/util.py
    CalendarServer/trunk/twext/web2/dav/element/xmlext.py
    CalendarServer/trunk/twext/web2/dav/fileop.py
    CalendarServer/trunk/twext/web2/dav/http.py
    CalendarServer/trunk/twext/web2/dav/idav.py
    CalendarServer/trunk/twext/web2/dav/method/
    CalendarServer/trunk/twext/web2/dav/method/__init__.py
    CalendarServer/trunk/twext/web2/dav/method/acl.py
    CalendarServer/trunk/twext/web2/dav/method/copymove.py
    CalendarServer/trunk/twext/web2/dav/method/delete.py
    CalendarServer/trunk/twext/web2/dav/method/get.py
    CalendarServer/trunk/twext/web2/dav/method/lock.py
    CalendarServer/trunk/twext/web2/dav/method/mkcol.py
    CalendarServer/trunk/twext/web2/dav/method/prop_common.py
    CalendarServer/trunk/twext/web2/dav/method/propfind.py
    CalendarServer/trunk/twext/web2/dav/method/proppatch.py
    CalendarServer/trunk/twext/web2/dav/method/put.py
    CalendarServer/trunk/twext/web2/dav/method/put_common.py
    CalendarServer/trunk/twext/web2/dav/method/report.py
    CalendarServer/trunk/twext/web2/dav/method/report_acl_principal_prop_set.py
    CalendarServer/trunk/twext/web2/dav/method/report_expand.py
    CalendarServer/trunk/twext/web2/dav/method/report_principal_match.py
    CalendarServer/trunk/twext/web2/dav/method/report_principal_property_search.py
    CalendarServer/trunk/twext/web2/dav/method/report_principal_search_property_set.py
    CalendarServer/trunk/twext/web2/dav/noneprops.py
    CalendarServer/trunk/twext/web2/dav/resource.py
    CalendarServer/trunk/twext/web2/dav/static.py
    CalendarServer/trunk/twext/web2/dav/stream.py
    CalendarServer/trunk/twext/web2/dav/test/data/
    CalendarServer/trunk/twext/web2/dav/test/data/quota_100.txt
    CalendarServer/trunk/twext/web2/dav/test/data/xml/
    CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_bad.xml
    CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_nonamespace.xml
    CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_request.xml
    CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_response.xml
    CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPPATCH_request.xml
    CalendarServer/trunk/twext/web2/dav/test/data/xml/REPORT_request.xml
    CalendarServer/trunk/twext/web2/dav/test/data/xml/REPORT_response.xml
    CalendarServer/trunk/twext/web2/dav/test/test_acl.py
    CalendarServer/trunk/twext/web2/dav/test/test_copy.py
    CalendarServer/trunk/twext/web2/dav/test/test_delete.py
    CalendarServer/trunk/twext/web2/dav/test/test_http.py
    CalendarServer/trunk/twext/web2/dav/test/test_lock.py
    CalendarServer/trunk/twext/web2/dav/test/test_mkcol.py
    CalendarServer/trunk/twext/web2/dav/test/test_move.py
    CalendarServer/trunk/twext/web2/dav/test/test_options.py
    CalendarServer/trunk/twext/web2/dav/test/test_pipeline.py
    CalendarServer/trunk/twext/web2/dav/test/test_prop.py
    CalendarServer/trunk/twext/web2/dav/test/test_put.py
    CalendarServer/trunk/twext/web2/dav/test/test_quota.py
    CalendarServer/trunk/twext/web2/dav/test/test_report.py
    CalendarServer/trunk/twext/web2/dav/test/test_report_expand.py
    CalendarServer/trunk/twext/web2/dav/test/test_resource.py
    CalendarServer/trunk/twext/web2/dav/test/test_static.py
    CalendarServer/trunk/twext/web2/dav/test/test_stream.py
    CalendarServer/trunk/twext/web2/dav/test/test_util.py
    CalendarServer/trunk/twext/web2/dav/test/test_xattrprops.py
    CalendarServer/trunk/twext/web2/dav/test/test_xml.py
    CalendarServer/trunk/twext/web2/dav/test/test_xml_rfc3744.py
    CalendarServer/trunk/twext/web2/dav/test/tworequest_client.py
    CalendarServer/trunk/twext/web2/dav/test/util.py
    CalendarServer/trunk/twext/web2/dav/util.py
    CalendarServer/trunk/twext/web2/dav/xattrprops.py
    CalendarServer/trunk/twext/web2/dirlist.py
    CalendarServer/trunk/twext/web2/error.py
    CalendarServer/trunk/twext/web2/fileupload.py
    CalendarServer/trunk/twext/web2/filter/
    CalendarServer/trunk/twext/web2/filter/__init__.py
    CalendarServer/trunk/twext/web2/filter/gzip.py
    CalendarServer/trunk/twext/web2/filter/location.py
    CalendarServer/trunk/twext/web2/filter/range.py
    CalendarServer/trunk/twext/web2/http_headers.py
    CalendarServer/trunk/twext/web2/iweb.py
    CalendarServer/trunk/twext/web2/log.py
    CalendarServer/trunk/twext/web2/resource.py
    CalendarServer/trunk/twext/web2/responsecode.py
    CalendarServer/trunk/twext/web2/server.py
    CalendarServer/trunk/twext/web2/static.py
    CalendarServer/trunk/twext/web2/stream.py
    CalendarServer/trunk/twext/web2/test/
    CalendarServer/trunk/twext/web2/test/__init__.py
    CalendarServer/trunk/twext/web2/test/server.pem
    CalendarServer/trunk/twext/web2/test/simple_client.py
    CalendarServer/trunk/twext/web2/test/stream_data.txt
    CalendarServer/trunk/twext/web2/test/test_client.py
    CalendarServer/trunk/twext/web2/test/test_compat.py
    CalendarServer/trunk/twext/web2/test/test_fileupload.py
    CalendarServer/trunk/twext/web2/test/test_http.py
    CalendarServer/trunk/twext/web2/test/test_http_headers.py
    CalendarServer/trunk/twext/web2/test/test_httpauth.py
    CalendarServer/trunk/twext/web2/test/test_log.py
    CalendarServer/trunk/twext/web2/test/test_resource.py
    CalendarServer/trunk/twext/web2/test/test_server.py
    CalendarServer/trunk/twext/web2/test/test_static.py
    CalendarServer/trunk/twext/web2/test/test_stream.py

Removed Paths:
-------------
    CalendarServer/trunk/lib-patches/Twisted/
    CalendarServer/trunk/twext/web2/auth/__init__.py
    CalendarServer/trunk/twext/web2/auth/basic.py
    CalendarServer/trunk/twext/web2/auth/digest.py
    CalendarServer/trunk/twext/web2/auth/interfaces.py
    CalendarServer/trunk/twext/web2/auth/wrapper.py
    CalendarServer/trunk/twext/web2/client/__init__.py
    CalendarServer/trunk/twext/web2/client/http.py
    CalendarServer/trunk/twext/web2/client/interfaces.py
    CalendarServer/trunk/twext/web2/dav/element/__init__.py
    CalendarServer/trunk/twext/web2/dav/element/base.py
    CalendarServer/trunk/twext/web2/dav/element/extensions.py
    CalendarServer/trunk/twext/web2/dav/element/parser.py
    CalendarServer/trunk/twext/web2/dav/element/rfc2518.py
    CalendarServer/trunk/twext/web2/dav/element/rfc3253.py
    CalendarServer/trunk/twext/web2/dav/element/rfc3744.py
    CalendarServer/trunk/twext/web2/dav/element/rfc4331.py
    CalendarServer/trunk/twext/web2/dav/element/util.py
    CalendarServer/trunk/twext/web2/dav/element/xmlext.py
    CalendarServer/trunk/twext/web2/dav/method/__init__.py
    CalendarServer/trunk/twext/web2/dav/method/acl.py
    CalendarServer/trunk/twext/web2/dav/method/copymove.py
    CalendarServer/trunk/twext/web2/dav/method/delete.py
    CalendarServer/trunk/twext/web2/dav/method/get.py
    CalendarServer/trunk/twext/web2/dav/method/lock.py
    CalendarServer/trunk/twext/web2/dav/method/mkcol.py
    CalendarServer/trunk/twext/web2/dav/method/prop_common.py
    CalendarServer/trunk/twext/web2/dav/method/propfind.py
    CalendarServer/trunk/twext/web2/dav/method/proppatch.py
    CalendarServer/trunk/twext/web2/dav/method/put.py
    CalendarServer/trunk/twext/web2/dav/method/put_common.py
    CalendarServer/trunk/twext/web2/dav/method/report.py
    CalendarServer/trunk/twext/web2/dav/method/report_acl_principal_prop_set.py
    CalendarServer/trunk/twext/web2/dav/method/report_expand.py
    CalendarServer/trunk/twext/web2/dav/method/report_principal_match.py
    CalendarServer/trunk/twext/web2/dav/method/report_principal_property_search.py
    CalendarServer/trunk/twext/web2/dav/method/report_principal_search_property_set.py
    CalendarServer/trunk/twext/web2/dav/test/data/quota_100.txt
    CalendarServer/trunk/twext/web2/dav/test/data/xml/
    CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_bad.xml
    CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_nonamespace.xml
    CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_request.xml
    CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_response.xml
    CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPPATCH_request.xml
    CalendarServer/trunk/twext/web2/dav/test/data/xml/REPORT_request.xml
    CalendarServer/trunk/twext/web2/dav/test/data/xml/REPORT_response.xml
    CalendarServer/trunk/twext/web2/filter/__init__.py
    CalendarServer/trunk/twext/web2/filter/gzip.py
    CalendarServer/trunk/twext/web2/filter/location.py
    CalendarServer/trunk/twext/web2/filter/range.py
    CalendarServer/trunk/twext/web2/test/__init__.py
    CalendarServer/trunk/twext/web2/test/server.pem
    CalendarServer/trunk/twext/web2/test/simple_client.py
    CalendarServer/trunk/twext/web2/test/stream_data.txt
    CalendarServer/trunk/twext/web2/test/test_client.py
    CalendarServer/trunk/twext/web2/test/test_compat.py
    CalendarServer/trunk/twext/web2/test/test_fileupload.py
    CalendarServer/trunk/twext/web2/test/test_http.py
    CalendarServer/trunk/twext/web2/test/test_http_headers.py
    CalendarServer/trunk/twext/web2/test/test_httpauth.py
    CalendarServer/trunk/twext/web2/test/test_log.py
    CalendarServer/trunk/twext/web2/test/test_resource.py
    CalendarServer/trunk/twext/web2/test/test_server.py
    CalendarServer/trunk/twext/web2/test/test_static.py
    CalendarServer/trunk/twext/web2/test/test_stream.py

Property Changed:
----------------
    CalendarServer/trunk/


Property changes on: CalendarServer/trunk
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
   + /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093

Modified: CalendarServer/trunk/calendarserver/provision/root.py
===================================================================
--- CalendarServer/trunk/calendarserver/provision/root.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/calendarserver/provision/root.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -20,10 +20,10 @@
 
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.cred.error import LoginFailed, UnauthorizedLogin
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.http import HTTPError, StatusResponse
-from twisted.web2.auth.wrapper import UnauthorizedResponse
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2.auth.wrapper import UnauthorizedResponse
 from twisted.web.xmlrpc import Proxy
 
 from twext.log import Logger
@@ -70,7 +70,7 @@
             self.responseCache = DisabledCache()
 
         if config.ResponseCompression:
-            from twisted.web2.filter import gzip
+            from twext.web2.filter import gzip
             self.contentFilters.append((gzip.gzipfilter, True))
 
         if not config.EnableKeepAlive:

Modified: CalendarServer/trunk/calendarserver/provision/test/test_root.py
===================================================================
--- CalendarServer/trunk/calendarserver/provision/test/test_root.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/calendarserver/provision/test/test_root.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -18,15 +18,15 @@
 
 from twisted.cred.portal import Portal
 from twisted.internet.defer import inlineCallbacks, maybeDeferred
-from twisted.web2 import http_headers
-from twisted.web2 import responsecode
-from twisted.web2 import server
-from twisted.web2.auth import basic
-from twisted.web2.dav import auth
-from twisted.web2.dav import davxml
-from twisted.web2.http import HTTPError
-from twisted.web2.iweb import IResponse
-from twisted.web2.test.test_server import SimpleRequest
+from twext.web2 import http_headers
+from twext.web2 import responsecode
+from twext.web2 import server
+from twext.web2.auth import basic
+from twext.web2.dav import auth
+from twext.web2.dav import davxml
+from twext.web2.http import HTTPError
+from twext.web2.iweb import IResponse
+from twext.web2.test.test_server import SimpleRequest
 
 from twistedcaldav.test.util import TestCase
 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource

Modified: CalendarServer/trunk/calendarserver/sidecar/task.py
===================================================================
--- CalendarServer/trunk/calendarserver/sidecar/task.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/calendarserver/sidecar/task.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -31,7 +31,7 @@
 from twisted.internet.reactor import callLater
 from twisted.plugin import IPlugin
 from twisted.python.usage import Options, UsageError
-from twisted.web2.http_headers import Headers
+from twext.web2.http_headers import Headers
 
 from twext.log import Logger, LoggingMixIn
 from twext.log import logLevelForNamespace, setLogLevelForNamespace

Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -45,7 +45,7 @@
 from twisted.application.service import Service, MultiService, IServiceMaker
 from twisted.scripts.mktap import getid
 from twisted.runner import procmon
-from twisted.web2.server import Site
+from twext.web2.server import Site
 
 from twext.log import Logger, LoggingMixIn
 from twext.log import logLevelForNamespace, setLogLevelForNamespace

Modified: CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/test/test_caldav.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/calendarserver/tap/test/test_caldav.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -30,8 +30,8 @@
 from twisted.application.service import IService
 from twisted.application import internet
 
-from twisted.web2.dav import auth
-from twisted.web2.log import LogWrapperResource
+from twext.web2.dav import auth
+from twext.web2.log import LogWrapperResource
 
 from twext.python.plistlib import writePlist
 from twext.internet.tcp import MaxAcceptTCPServer, MaxAcceptSSLServer

Modified: CalendarServer/trunk/calendarserver/tools/gateway.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/gateway.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/calendarserver/tools/gateway.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -29,7 +29,7 @@
 from twisted.python.util import switchUID
 from twistedcaldav.config import config, ConfigurationError
 from twistedcaldav.directory.directory import DirectoryError
-from twisted.web2.dav import davxml
+from twext.web2.dav import davxml
 
 from calendarserver.tools.util import loadConfig, getDirectory, setupMemcached, setupNotifications
 from calendarserver.tools.principals import principalForPrincipalID, proxySubprincipal, addProxy, removeProxy, ProxyError, ProxyWarning

Modified: CalendarServer/trunk/calendarserver/tools/principals.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/principals.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/calendarserver/tools/principals.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -27,7 +27,7 @@
 from twisted.python.util import switchUID
 from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks
-from twisted.web2.dav import davxml
+from twext.web2.dav import davxml
 
 from twext.log import setLogLevelForNamespace
 from twext.python.log import StandardIOObserver

Modified: CalendarServer/trunk/calendarserver/tools/test/test_gateway.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_gateway.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/calendarserver/tools/test/test_gateway.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -18,7 +18,7 @@
 import plistlib
 import xml
 
-from twisted.python.filepath import FilePath
+from twext.python.filepath import CachingFilePath as FilePath
 from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks, Deferred, returnValue
 

Modified: CalendarServer/trunk/calendarserver/tools/test/test_principals.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_principals.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/calendarserver/tools/test/test_principals.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -16,7 +16,7 @@
 
 import os
 
-from twisted.python.filepath import FilePath
+from twext.python.filepath import CachingFilePath as FilePath
 from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks, Deferred, returnValue
 

Modified: CalendarServer/trunk/calendarserver/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/util.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/calendarserver/util.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -26,10 +26,10 @@
 from twisted.python.reflect import namedClass
 from twisted.internet import reactor
 from twisted.cred.portal import Portal
-from twisted.web2.dav import auth
-from twisted.web2.auth.basic import BasicCredentialFactory
-from twisted.web2.static import File as FileResource
-from twisted.python.filepath import FilePath
+from twext.web2.dav import auth
+from twext.web2.auth.basic import BasicCredentialFactory
+from twext.web2.static import File as FileResource
+from twext.python.filepath import CachingFilePath as FilePath
 
 from twext.log import Logger
 

Modified: CalendarServer/trunk/calendarserver/webadmin/resource.py
===================================================================
--- CalendarServer/trunk/calendarserver/webadmin/resource.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/calendarserver/webadmin/resource.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -32,10 +32,10 @@
 from twistedcaldav.extensions import DAVFile, ReadOnlyResourceMixIn
 
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2.http import Response
-from twisted.web2.http_headers import MimeType
-from twisted.web2.stream import MemoryStream
-from twisted.web2.dav import davxml
+from twext.web2.http import Response
+from twext.web2.http_headers import MimeType
+from twext.web2.stream import MemoryStream
+from twext.web2.dav import davxml
 
 
 class WebAdminResource (ReadOnlyResourceMixIn, DAVFile):

Modified: CalendarServer/trunk/calendarserver/webcal/resource.py
===================================================================
--- CalendarServer/trunk/calendarserver/webcal/resource.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/calendarserver/webcal/resource.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -28,12 +28,12 @@
 from urlparse import urlparse
 from cgi import parse_qs
 
-from twisted.web2 import responsecode
-from twisted.web2.http import Response
-from twisted.web2.http_headers import MimeType
-from twisted.web2.stream import MemoryStream
-from twisted.web2.dav import davxml
-from twisted.web2.dav.resource import TwistedACLInheritable
+from twext.web2 import responsecode
+from twext.web2.http import Response
+from twext.web2.http_headers import MimeType
+from twext.web2.stream import MemoryStream
+from twext.web2.dav import davxml
+from twext.web2.dav.resource import TwistedACLInheritable
 
 from twistedcaldav.config import config
 from twistedcaldav.extensions import DAVFile, ReadOnlyResourceMixIn

Modified: CalendarServer/trunk/setup.py
===================================================================
--- CalendarServer/trunk/setup.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/setup.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -46,7 +46,7 @@
 # Options
 #
 
-description = "CalDAV/CardDAV protocol extensions to twisted.web2.dav",
+description = "CalDAV/CardDAV protocol extensions to twext.web2.dav",
 long_description = """
 Extends twisted.web2.dav to implement CalDAV/CardDAV-aware resources and methods.
 """

Modified: CalendarServer/trunk/support/build.sh
===================================================================
--- CalendarServer/trunk/support/build.sh	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/support/build.sh	2010-02-18 00:11:20 UTC (rev 5150)
@@ -84,7 +84,6 @@
       top="$(cd "${caldav}/.." && pwd -L)";
   patches="${caldav}/lib-patches";
   twisted="${top}/Twisted";
-      dav="${twisted}/twisted/web2/dav";
 
   # Find a command that can hash up a string for us
   if type -t openssl > /dev/null; then
@@ -471,19 +470,9 @@
     false false false false 0;
 
   py_dependency "Twisted" "twisted" "Twisted" \
-    "svn" "svn://svn.twistedmatrix.com/svn/Twisted/branches/dav-take-two-3081-4" \
-    false true true false 27622;
+    "svn" "svn://svn.twistedmatrix.com/svn/Twisted/tags/releases/twisted-9.0.0" \
+    false true true false 27606;
 
-  # twisted.web2 doesn't get installed by default, so in the install phase
-  # let's make sure it does.
-  if [ -n "${install}" ]; then
-    echo "";
-    echo "Installing Twisted.web2...";
-    cd "${twisted}";
-    "${python}" ./twisted/web2/topfiles/setup.py install "${install_flag}${install}";
-    cd /;
-  fi;
-
   py_dependency "dateutil" "dateutil" "python-dateutil-1.4.1" \
     "www" "http://www.labix.org/download/python-dateutil/python-dateutil-1.4.1.tar.gz" \
     false false false false 0;

Modified: CalendarServer/trunk/twext/__init__.py
===================================================================
--- CalendarServer/trunk/twext/__init__.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -17,3 +17,6 @@
 """
 Extentions to the Twisted Framework.
 """
+
+from twext import patches
+patches                         # pacify pyflakes

Copied: CalendarServer/trunk/twext/patches.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/patches.py)
===================================================================
--- CalendarServer/trunk/twext/patches.py	                        (rev 0)
+++ CalendarServer/trunk/twext/patches.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,23 @@
+##
+# Copyright (c) 2005-2010 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.
+##
+
+"""
+Patches for behavior in Twisted which calendarserver requires to be different.
+"""
+
+from twisted.mail.imap4 import Command
+
+Command._1_RESPONSES += tuple(['BYE'])

Copied: CalendarServer/trunk/twext/python/filepath.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/python/filepath.py)
===================================================================
--- CalendarServer/trunk/twext/python/filepath.py	                        (rev 0)
+++ CalendarServer/trunk/twext/python/filepath.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,88 @@
+##
+# Copyright (c) 2010 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.
+##
+
+"""
+Extend L{twisted.python.filepath} to provide performance enhancements for
+calendar server.
+"""
+
+from twisted.python.filepath import FilePath
+
+from stat import S_ISDIR
+
+class CachingFilePath(FilePath, object):
+    """
+    A descendent of L{twisted.python.filepath.FilePath} which implements a more
+    aggressive caching policy.
+    """
+
+    def __init__(self, path, alwaysCreate=False):
+        super(CachingFilePath, self).__init__(path, alwaysCreate)
+        self.existsCached = None
+        self.isDirCached = None
+
+
+    def changed(self):
+        """
+        This path may have changed in the filesystem, so forget all cached
+        information about it.
+        """
+        self.statinfo = None
+        self.existsCached = None
+        self.isDirCached = None
+
+
+    def restat(self, reraise=True):
+        """
+        Re-cache stat information.
+        """
+        try:
+            return super(CachingFilePath, self).restat(reraise)
+        finally:
+            if self.statinfo:
+                self.existsCached = True
+                self.isDirCached = S_ISDIR(self.statinfo.st_mode)
+            else:
+                self.existsCached = False
+                self.isDirCached = None
+
+
+    def moveTo(self, destination, followLinks=True):
+        """
+        Override L{FilePath.moveTo}, updating extended cache information if
+        necessary.
+        """
+        try:
+            return super(CachingFilePath, self).moveTo(destination, followLinks)
+        except OSError:
+            raise
+        else:
+            self.changed()
+
+
+    def remove(self):
+        """
+        Override L{FilePath.remove}, updating extended cache information if
+        necessary.
+        """
+        try:
+            return super(CachingFilePath, self).remove()
+        finally:
+            self.changed()
+
+CachingFilePath.clonePath = CachingFilePath
+
+__all__ = ['CachingFilePath']

Modified: CalendarServer/trunk/twext/web2/__init__.py
===================================================================
--- CalendarServer/trunk/twext/web2/__init__.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,19 +1,41 @@
+# -*- test-case-name: twext.web2.test -*-
+
 ##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# Copyright (c) 2010 Apple Computer, 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
+# 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:
 #
-# http://www.apache.org/licenses/LICENSE-2.0
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
 #
-# 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.
+# 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.
+#
 ##
 
 """
-Extentions to twisted.web2
+twext.web2: a transitional package for Calendar Server to move from a
+dependence on twisted.web2 to twisted.web.
+
+This is a copy of (most of) twisted.web2, but the intention is for this package
+to disappear and gradually get replaced with twisted.web.
+
+Features from this package are being merged into L{twisted.web}.  Once that
+is complete, this package will be removed.
+
+See U{http://twistedmatrix.com/trac/wiki/WebDevelopmentWithTwisted}.
 """
+
+from twext.web2._version import version
+__version__ = version.short()

Copied: CalendarServer/trunk/twext/web2/_version.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/_version.py)
===================================================================
--- CalendarServer/trunk/twext/web2/_version.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/_version.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,3 @@
+# This is an auto-generated file. Do not edit it.
+from twisted.python import versions
+version = versions.Version('twext.web2', 9, 0, 0)

Deleted: CalendarServer/trunk/twext/web2/auth/__init__.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/auth/__init__.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/auth/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,27 +0,0 @@
-##
-# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
-# Copyright (c) 2010 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.
-#
-##
-
-"""
-Client and server implementations of http authentication
-"""

Copied: CalendarServer/trunk/twext/web2/auth/__init__.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/auth/__init__.py)
===================================================================
--- CalendarServer/trunk/twext/web2/auth/__init__.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/auth/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,27 @@
+##
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.
+#
+##
+
+"""
+Client and server implementations of http authentication
+"""

Deleted: CalendarServer/trunk/twext/web2/auth/basic.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/auth/basic.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/auth/basic.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,67 +0,0 @@
-# -*- test-case-name: twext.web2.test.test_httpauth -*-
-##
-# Copyright (c) 2006-2009 Twisted Matrix Laboratories.
-# Copyright (c) 2010 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.cred import credentials, error
-from twisted.internet.defer import succeed, fail
-from twext.web2.auth.interfaces import ICredentialFactory
-
-from zope.interface import implements
-
-class BasicCredentialFactory(object):
-    """
-    Credential Factory for HTTP Basic Authentication
-    """
-
-    implements(ICredentialFactory)
-
-    scheme = 'basic'
-
-    def __init__(self, realm):
-        self.realm = realm
-
-
-    def getChallenge(self, peer):
-        """
-        @see L{ICredentialFactory.getChallenge}
-        """
-        return succeed({'realm': self.realm})
-
-
-    def decode(self, response, request):
-        """
-        Decode the credentials for basic auth.
-
-        @see L{ICredentialFactory.decode}
-        """
-        try:
-            creds = (response + '===').decode('base64')
-        except:
-            raise error.LoginFailed('Invalid credentials')
-
-        creds = creds.split(':', 1)
-        if len(creds) == 2:
-            return succeed(credentials.UsernamePassword(*creds))
-        else:
-            return fail(error.LoginFailed('Invalid credentials'))

Copied: CalendarServer/trunk/twext/web2/auth/basic.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/auth/basic.py)
===================================================================
--- CalendarServer/trunk/twext/web2/auth/basic.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/auth/basic.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,67 @@
+# -*- test-case-name: twext.web2.test.test_httpauth -*-
+##
+# Copyright (c) 2006-2009 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.cred import credentials, error
+from twisted.internet.defer import succeed, fail
+from twext.web2.auth.interfaces import ICredentialFactory
+
+from zope.interface import implements
+
+class BasicCredentialFactory(object):
+    """
+    Credential Factory for HTTP Basic Authentication
+    """
+
+    implements(ICredentialFactory)
+
+    scheme = 'basic'
+
+    def __init__(self, realm):
+        self.realm = realm
+
+
+    def getChallenge(self, peer):
+        """
+        @see L{ICredentialFactory.getChallenge}
+        """
+        return succeed({'realm': self.realm})
+
+
+    def decode(self, response, request):
+        """
+        Decode the credentials for basic auth.
+
+        @see L{ICredentialFactory.decode}
+        """
+        try:
+            creds = (response + '===').decode('base64')
+        except:
+            raise error.LoginFailed('Invalid credentials')
+
+        creds = creds.split(':', 1)
+        if len(creds) == 2:
+            return succeed(credentials.UsernamePassword(*creds))
+        else:
+            return fail(error.LoginFailed('Invalid credentials'))

Deleted: CalendarServer/trunk/twext/web2/auth/digest.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/auth/digest.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/auth/digest.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,398 +0,0 @@
-# -*- test-case-name: twext.web2.test.test_httpauth -*-
-##
-# Copyright (c) 2006-2009 Twisted Matrix Laboratories.
-# Copyright (c) 2010 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.
-#
-##
-
-"""
-Implementation of RFC2617: HTTP Digest Authentication
-
-http://www.faqs.org/rfcs/rfc2617.html
-"""
-import sys
-import time
-import random
-
-from zope.interface import implements, Interface
-
-from twisted.python.hashlib import md5, sha1
-from twisted.cred import credentials, error
-from twisted.internet.defer import succeed
-from twext.web2.auth.interfaces import ICredentialFactory
-from twext.web2.http_headers import Token, tokenize, split, parseKeyValue
-
-
-# The digest math
-
-algorithms = {
-    'md5': md5,
-    'md5-sess': md5,
-    'sha': sha1,
-}
-
-# DigestCalcHA1
-def calcHA1(
-    pszAlg,
-    pszUserName,
-    pszRealm,
-    pszPassword,
-    pszNonce,
-    pszCNonce,
-    preHA1=None
-):
-    """
-    @param pszAlg: The name of the algorithm to use to calculate the digest.
-        Currently supported are md5 md5-sess and sha.
-
-    @param pszUserName: The username
-    @param pszRealm: The realm
-    @param pszPassword: The password
-    @param pszNonce: The nonce
-    @param pszCNonce: The cnonce
-
-    @param preHA1: If available this is a str containing a previously
-       calculated HA1 as a hex string. If this is given then the values for
-       pszUserName, pszRealm, and pszPassword are ignored.
-    """
-
-    if (preHA1 and (pszUserName or pszRealm or pszPassword)):
-        raise TypeError(("preHA1 is incompatible with the pszUserName, "
-                         "pszRealm, and pszPassword arguments"))
-
-    if preHA1 is None:
-        # We need to calculate the HA1 from the username:realm:password
-        m = algorithms[pszAlg]()
-        m.update(pszUserName)
-        m.update(":")
-        m.update(pszRealm)
-        m.update(":")
-        m.update(pszPassword)
-        HA1 = m.digest()
-    else:
-        # We were given a username:realm:password
-        HA1 = preHA1.decode('hex')
-
-    if pszAlg == "md5-sess":
-        m = algorithms[pszAlg]()
-        m.update(HA1)
-        m.update(":")
-        m.update(pszNonce)
-        m.update(":")
-        m.update(pszCNonce)
-        HA1 = m.digest()
-
-    return HA1.encode('hex')
-
-# DigestCalcResponse
-def calcResponse(
-    HA1,
-    algo,
-    pszNonce,
-    pszNonceCount,
-    pszCNonce,
-    pszQop,
-    pszMethod,
-    pszDigestUri,
-    pszHEntity,
-):
-    m = algorithms[algo]()
-    m.update(pszMethod)
-    m.update(":")
-    m.update(pszDigestUri)
-    if pszQop == "auth-int":
-        m.update(":")
-        m.update(pszHEntity)
-    HA2 = m.digest().encode('hex')
-
-    m = algorithms[algo]()
-    m.update(HA1)
-    m.update(":")
-    m.update(pszNonce)
-    m.update(":")
-    if pszNonceCount and pszCNonce: # pszQop:
-        m.update(pszNonceCount)
-        m.update(":")
-        m.update(pszCNonce)
-        m.update(":")
-        m.update(pszQop)
-        m.update(":")
-    m.update(HA2)
-    respHash = m.digest().encode('hex')
-    return respHash
-
-
-class IUsernameDigestHash(Interface):
-    """
-    This credential is used when a CredentialChecker has access to the hash
-    of the username:realm:password as in an Apache .htdigest file.
-    """
-    def checkHash(self, digestHash):
-        """
-        @param digestHash: The hashed username:realm:password to check against.
-
-        @return: a deferred which becomes, or a boolean indicating if the
-            hash matches.
-        """
-
-
-class DigestedCredentials:
-    """Yet Another Simple HTTP Digest authentication scheme"""
-
-    implements(credentials.IUsernameHashedPassword,
-               IUsernameDigestHash)
-
-    def __init__(self, username, method, realm, fields, originalMethod=None):
-        self.username = username
-        self.method = method
-        self.realm = realm
-        self.fields = fields
-        if originalMethod:
-            self.originalMethod = originalMethod
-        else:
-            self.originalMethod = method
-
-    def checkPassword(self, password):
-        response = self.fields.get('response')
-        uri = self.fields.get('uri')
-        nonce = self.fields.get('nonce')
-        cnonce = self.fields.get('cnonce')
-        nc = self.fields.get('nc')
-        algo = self.fields.get('algorithm', 'md5').lower()
-        qop = self.fields.get('qop', 'auth')
-
-        expected = calcResponse(
-            calcHA1(algo, self.username, self.realm, password, nonce, cnonce),
-            algo, nonce, nc, cnonce, qop, self.originalMethod, 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].
-        if nc is not None or cnonce is not None:
-            expected = calcResponse(
-                calcHA1(algo, self.username, self.realm, password, nonce, cnonce),
-                algo, nonce, None, None, qop, self.originalMethod, uri, None
-            )
-            if expected == response:
-                return True
-
-    def checkHash(self, digestHash):
-        response = self.fields.get('response')
-        uri = self.fields.get('uri')
-        nonce = self.fields.get('nonce')
-        cnonce = self.fields.get('cnonce')
-        nc = self.fields.get('nc')
-        algo = self.fields.get('algorithm', 'md5').lower()
-        qop = self.fields.get('qop', 'auth')
-
-        expected = calcResponse(
-            calcHA1(algo, None, None, None, nonce, cnonce, preHA1=digestHash),
-            algo, nonce, nc, cnonce, qop, self.originalMethod, uri, None
-        )
-
-        return expected == response
-
-
-class DigestCredentialFactory(object):
-    """
-    Support for RFC2617 HTTP Digest Authentication
-
-    @cvar CHALLENGE_LIFETIME_SECS: The number of seconds for which an
-        opaque should be valid.
-
-    @ivar privateKey: A random string used for generating the secure opaque.
-    """
-
-    implements(ICredentialFactory)
-
-    CHALLENGE_LIFETIME_SECS = 15 * 60    # 15 minutes
-
-    scheme = "digest"
-
-    def __init__(self, algorithm, realm):
-        """
-        @type algorithm: C{str}
-        @param algorithm: case insensitive string that specifies
-            the hash algorithm used, should be either, md5, md5-sess
-            or sha
-
-        @type realm: C{str}
-        @param realm: case sensitive string that specifies the realm
-            portion of the challenge
-        """
-        self.algorithm = algorithm
-        self.realm = realm
-
-        c = tuple([random.randrange(sys.maxint) for _ in range(3)])
-
-        self.privateKey = '%d%d%d' % c
-
-    def generateNonce(self):
-        c = tuple([random.randrange(sys.maxint) for _ in range(3)])
-        c = '%d%d%d' % c
-        return c
-
-    def _getTime(self):
-        """
-        Parameterize the time based seed used in generateOpaque
-        so we can deterministically unittest it's behavior.
-        """
-        return time.time()
-
-    def generateOpaque(self, nonce, clientip):
-        """
-        Generate an opaque to be returned to the client.
-        This should be a unique string that can be returned to us and verified.
-        """
-
-        # 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(key + self.privateKey).hexdigest()
-        ekey = key.encode('base64')
-        return "%s-%s" % (digest, ekey.replace('\n', ''))
-
-    def verifyOpaque(self, opaque, nonce, clientip):
-        """
-        Given the opaque and nonce from the request, as well as the clientip
-        that made the request, verify that the opaque was generated by us.
-        And that it's not too old.
-
-        @param opaque: The opaque value from the Digest response
-        @param nonce: The nonce value from the Digest response
-        @param clientip: The remote IP address of the client making the request
-
-        @return: C{True} if the opaque was successfully verified.
-
-        @raise error.LoginFailed: if C{opaque} could not be parsed or
-            contained the wrong values.
-        """
-
-        # First split the digest from the key
-        opaqueParts = opaque.split('-')
-        if len(opaqueParts) != 2:
-            raise error.LoginFailed('Invalid response, invalid opaque value')
-
-        # Verify the key
-        key = opaqueParts[1].decode('base64')
-        keyParts = key.split(',')
-
-        if len(keyParts) != 3:
-            raise error.LoginFailed('Invalid response, invalid opaque value')
-
-        if keyParts[0] != nonce:
-            raise error.LoginFailed(
-                'Invalid response, incompatible opaque/nonce values')
-
-        if keyParts[1] != clientip:
-            raise error.LoginFailed(
-                'Invalid response, incompatible opaque/client values')
-
-        if (int(self._getTime()) - int(keyParts[2]) >
-            DigestCredentialFactory.CHALLENGE_LIFETIME_SECS):
-
-            raise error.LoginFailed(
-                'Invalid response, incompatible opaque/nonce too old')
-
-        # Verify the digest
-        digest = md5(key + self.privateKey).hexdigest()
-        if digest != opaqueParts[0]:
-            raise error.LoginFailed('Invalid response, invalid opaque value')
-
-        return True
-
-    def getChallenge(self, peer):
-        """
-        Generate the challenge for use in the WWW-Authenticate header
-
-        @param peer: The L{IAddress} of the requesting client.
-
-        @return: The C{dict} that can be used to generate a WWW-Authenticate
-            header.
-        """
-
-        c = self.generateNonce()
-        o = self.generateOpaque(c, peer.host)
-
-        return succeed({'nonce': c,
-            'opaque': o,
-            'qop': 'auth',
-            'algorithm': self.algorithm,
-            'realm': self.realm,
-        })
-
-    def decode(self, response, request):
-        """
-        Decode the given response and attempt to generate a
-        L{DigestedCredentials} from it.
-
-        @type response: C{str}
-        @param response: A string of comma seperated key=value pairs
-
-        @type request: L{twext.web2.server.Request}
-        @param request: the request being processed
-
-        @return: L{DigestedCredentials}
-
-        @raise: L{error.LoginFailed} if the response does not contain a
-            username, a nonce, an opaque, or if the opaque is invalid.
-        """
-        response = ' '.join(response.splitlines())
-        
-        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.')
-
-        if 'opaque' not in auth:
-            raise error.LoginFailed('Invalid response, no opaque given.')
-
-        if 'nonce' not in auth:
-            raise error.LoginFailed('Invalid response, no nonce given.')
-
-        # Now verify the nonce/opaque values for this client
-        if self.verifyOpaque(auth.get('opaque'),
-                             auth.get('nonce'),
-                             request.remoteAddr.host):
-
-            if hasattr(request, "originalMethod"):
-                originalMethod = request.originalMethod
-            else:
-                originalMethod = None
-
-            return succeed(DigestedCredentials(username,
-                                               request.method,
-                                               self.realm,
-                                               auth,
-                                               originalMethod))

Copied: CalendarServer/trunk/twext/web2/auth/digest.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/auth/digest.py)
===================================================================
--- CalendarServer/trunk/twext/web2/auth/digest.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/auth/digest.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,398 @@
+# -*- test-case-name: twext.web2.test.test_httpauth -*-
+##
+# Copyright (c) 2006-2009 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.
+#
+##
+
+"""
+Implementation of RFC2617: HTTP Digest Authentication
+
+http://www.faqs.org/rfcs/rfc2617.html
+"""
+import sys
+import time
+import random
+
+from zope.interface import implements, Interface
+
+from twisted.python.hashlib import md5, sha1
+from twisted.cred import credentials, error
+from twisted.internet.defer import succeed
+from twext.web2.auth.interfaces import ICredentialFactory
+from twext.web2.http_headers import Token, tokenize, split, parseKeyValue
+
+
+# The digest math
+
+algorithms = {
+    'md5': md5,
+    'md5-sess': md5,
+    'sha': sha1,
+}
+
+# DigestCalcHA1
+def calcHA1(
+    pszAlg,
+    pszUserName,
+    pszRealm,
+    pszPassword,
+    pszNonce,
+    pszCNonce,
+    preHA1=None
+):
+    """
+    @param pszAlg: The name of the algorithm to use to calculate the digest.
+        Currently supported are md5 md5-sess and sha.
+
+    @param pszUserName: The username
+    @param pszRealm: The realm
+    @param pszPassword: The password
+    @param pszNonce: The nonce
+    @param pszCNonce: The cnonce
+
+    @param preHA1: If available this is a str containing a previously
+       calculated HA1 as a hex string. If this is given then the values for
+       pszUserName, pszRealm, and pszPassword are ignored.
+    """
+
+    if (preHA1 and (pszUserName or pszRealm or pszPassword)):
+        raise TypeError(("preHA1 is incompatible with the pszUserName, "
+                         "pszRealm, and pszPassword arguments"))
+
+    if preHA1 is None:
+        # We need to calculate the HA1 from the username:realm:password
+        m = algorithms[pszAlg]()
+        m.update(pszUserName)
+        m.update(":")
+        m.update(pszRealm)
+        m.update(":")
+        m.update(pszPassword)
+        HA1 = m.digest()
+    else:
+        # We were given a username:realm:password
+        HA1 = preHA1.decode('hex')
+
+    if pszAlg == "md5-sess":
+        m = algorithms[pszAlg]()
+        m.update(HA1)
+        m.update(":")
+        m.update(pszNonce)
+        m.update(":")
+        m.update(pszCNonce)
+        HA1 = m.digest()
+
+    return HA1.encode('hex')
+
+# DigestCalcResponse
+def calcResponse(
+    HA1,
+    algo,
+    pszNonce,
+    pszNonceCount,
+    pszCNonce,
+    pszQop,
+    pszMethod,
+    pszDigestUri,
+    pszHEntity,
+):
+    m = algorithms[algo]()
+    m.update(pszMethod)
+    m.update(":")
+    m.update(pszDigestUri)
+    if pszQop == "auth-int":
+        m.update(":")
+        m.update(pszHEntity)
+    HA2 = m.digest().encode('hex')
+
+    m = algorithms[algo]()
+    m.update(HA1)
+    m.update(":")
+    m.update(pszNonce)
+    m.update(":")
+    if pszNonceCount and pszCNonce: # pszQop:
+        m.update(pszNonceCount)
+        m.update(":")
+        m.update(pszCNonce)
+        m.update(":")
+        m.update(pszQop)
+        m.update(":")
+    m.update(HA2)
+    respHash = m.digest().encode('hex')
+    return respHash
+
+
+class IUsernameDigestHash(Interface):
+    """
+    This credential is used when a CredentialChecker has access to the hash
+    of the username:realm:password as in an Apache .htdigest file.
+    """
+    def checkHash(self, digestHash):
+        """
+        @param digestHash: The hashed username:realm:password to check against.
+
+        @return: a deferred which becomes, or a boolean indicating if the
+            hash matches.
+        """
+
+
+class DigestedCredentials:
+    """Yet Another Simple HTTP Digest authentication scheme"""
+
+    implements(credentials.IUsernameHashedPassword,
+               IUsernameDigestHash)
+
+    def __init__(self, username, method, realm, fields, originalMethod=None):
+        self.username = username
+        self.method = method
+        self.realm = realm
+        self.fields = fields
+        if originalMethod:
+            self.originalMethod = originalMethod
+        else:
+            self.originalMethod = method
+
+    def checkPassword(self, password):
+        response = self.fields.get('response')
+        uri = self.fields.get('uri')
+        nonce = self.fields.get('nonce')
+        cnonce = self.fields.get('cnonce')
+        nc = self.fields.get('nc')
+        algo = self.fields.get('algorithm', 'md5').lower()
+        qop = self.fields.get('qop', 'auth')
+
+        expected = calcResponse(
+            calcHA1(algo, self.username, self.realm, password, nonce, cnonce),
+            algo, nonce, nc, cnonce, qop, self.originalMethod, 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].
+        if nc is not None or cnonce is not None:
+            expected = calcResponse(
+                calcHA1(algo, self.username, self.realm, password, nonce, cnonce),
+                algo, nonce, None, None, qop, self.originalMethod, uri, None
+            )
+            if expected == response:
+                return True
+
+    def checkHash(self, digestHash):
+        response = self.fields.get('response')
+        uri = self.fields.get('uri')
+        nonce = self.fields.get('nonce')
+        cnonce = self.fields.get('cnonce')
+        nc = self.fields.get('nc')
+        algo = self.fields.get('algorithm', 'md5').lower()
+        qop = self.fields.get('qop', 'auth')
+
+        expected = calcResponse(
+            calcHA1(algo, None, None, None, nonce, cnonce, preHA1=digestHash),
+            algo, nonce, nc, cnonce, qop, self.originalMethod, uri, None
+        )
+
+        return expected == response
+
+
+class DigestCredentialFactory(object):
+    """
+    Support for RFC2617 HTTP Digest Authentication
+
+    @cvar CHALLENGE_LIFETIME_SECS: The number of seconds for which an
+        opaque should be valid.
+
+    @ivar privateKey: A random string used for generating the secure opaque.
+    """
+
+    implements(ICredentialFactory)
+
+    CHALLENGE_LIFETIME_SECS = 15 * 60    # 15 minutes
+
+    scheme = "digest"
+
+    def __init__(self, algorithm, realm):
+        """
+        @type algorithm: C{str}
+        @param algorithm: case insensitive string that specifies
+            the hash algorithm used, should be either, md5, md5-sess
+            or sha
+
+        @type realm: C{str}
+        @param realm: case sensitive string that specifies the realm
+            portion of the challenge
+        """
+        self.algorithm = algorithm
+        self.realm = realm
+
+        c = tuple([random.randrange(sys.maxint) for _ in range(3)])
+
+        self.privateKey = '%d%d%d' % c
+
+    def generateNonce(self):
+        c = tuple([random.randrange(sys.maxint) for _ in range(3)])
+        c = '%d%d%d' % c
+        return c
+
+    def _getTime(self):
+        """
+        Parameterize the time based seed used in generateOpaque
+        so we can deterministically unittest it's behavior.
+        """
+        return time.time()
+
+    def generateOpaque(self, nonce, clientip):
+        """
+        Generate an opaque to be returned to the client.
+        This should be a unique string that can be returned to us and verified.
+        """
+
+        # 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(key + self.privateKey).hexdigest()
+        ekey = key.encode('base64')
+        return "%s-%s" % (digest, ekey.replace('\n', ''))
+
+    def verifyOpaque(self, opaque, nonce, clientip):
+        """
+        Given the opaque and nonce from the request, as well as the clientip
+        that made the request, verify that the opaque was generated by us.
+        And that it's not too old.
+
+        @param opaque: The opaque value from the Digest response
+        @param nonce: The nonce value from the Digest response
+        @param clientip: The remote IP address of the client making the request
+
+        @return: C{True} if the opaque was successfully verified.
+
+        @raise error.LoginFailed: if C{opaque} could not be parsed or
+            contained the wrong values.
+        """
+
+        # First split the digest from the key
+        opaqueParts = opaque.split('-')
+        if len(opaqueParts) != 2:
+            raise error.LoginFailed('Invalid response, invalid opaque value')
+
+        # Verify the key
+        key = opaqueParts[1].decode('base64')
+        keyParts = key.split(',')
+
+        if len(keyParts) != 3:
+            raise error.LoginFailed('Invalid response, invalid opaque value')
+
+        if keyParts[0] != nonce:
+            raise error.LoginFailed(
+                'Invalid response, incompatible opaque/nonce values')
+
+        if keyParts[1] != clientip:
+            raise error.LoginFailed(
+                'Invalid response, incompatible opaque/client values')
+
+        if (int(self._getTime()) - int(keyParts[2]) >
+            DigestCredentialFactory.CHALLENGE_LIFETIME_SECS):
+
+            raise error.LoginFailed(
+                'Invalid response, incompatible opaque/nonce too old')
+
+        # Verify the digest
+        digest = md5(key + self.privateKey).hexdigest()
+        if digest != opaqueParts[0]:
+            raise error.LoginFailed('Invalid response, invalid opaque value')
+
+        return True
+
+    def getChallenge(self, peer):
+        """
+        Generate the challenge for use in the WWW-Authenticate header
+
+        @param peer: The L{IAddress} of the requesting client.
+
+        @return: The C{dict} that can be used to generate a WWW-Authenticate
+            header.
+        """
+
+        c = self.generateNonce()
+        o = self.generateOpaque(c, peer.host)
+
+        return succeed({'nonce': c,
+            'opaque': o,
+            'qop': 'auth',
+            'algorithm': self.algorithm,
+            'realm': self.realm,
+        })
+
+    def decode(self, response, request):
+        """
+        Decode the given response and attempt to generate a
+        L{DigestedCredentials} from it.
+
+        @type response: C{str}
+        @param response: A string of comma seperated key=value pairs
+
+        @type request: L{twext.web2.server.Request}
+        @param request: the request being processed
+
+        @return: L{DigestedCredentials}
+
+        @raise: L{error.LoginFailed} if the response does not contain a
+            username, a nonce, an opaque, or if the opaque is invalid.
+        """
+        response = ' '.join(response.splitlines())
+        
+        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.')
+
+        if 'opaque' not in auth:
+            raise error.LoginFailed('Invalid response, no opaque given.')
+
+        if 'nonce' not in auth:
+            raise error.LoginFailed('Invalid response, no nonce given.')
+
+        # Now verify the nonce/opaque values for this client
+        if self.verifyOpaque(auth.get('opaque'),
+                             auth.get('nonce'),
+                             request.remoteAddr.host):
+
+            if hasattr(request, "originalMethod"):
+                originalMethod = request.originalMethod
+            else:
+                originalMethod = None
+
+            return succeed(DigestedCredentials(username,
+                                               request.method,
+                                               self.realm,
+                                               auth,
+                                               originalMethod))

Deleted: CalendarServer/trunk/twext/web2/auth/interfaces.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/auth/interfaces.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/auth/interfaces.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,83 +0,0 @@
-##
-# Copyright (c) 2004-2007 Twisted Matrix Laboratories.
-# Copyright (c) 2010 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 zope.interface import Interface, Attribute
-
-class ICredentialFactory(Interface):
-    """
-    A credential factory provides state between stages in HTTP
-    authentication.  It is ultimately in charge of creating an
-    ICredential for the specified scheme, that will be used by
-    cred to complete authentication.
-    """
-    scheme = Attribute(("string indicating the authentication scheme "
-                        "this factory is associated with."))
-
-    def getChallenge(peer):
-        """
-        Generate a challenge the client may respond to.
-
-        @type peer: L{twisted.internet.interfaces.IAddress}
-        @param peer: The client's address
-
-        @rtype: C{dict}
-        @return: Deferred returning dictionary of challenge arguments
-        """
-
-    def decode(response, request):
-        """
-        Create a credentials object from the given response.
-        May raise twisted.cred.error.LoginFailed if the response is invalid.
-
-        @type response: C{str}
-        @param response: scheme specific response string
-
-        @type request: L{twext.web2.server.Request}
-        @param request: the request being processed
-
-        @return: Deferred returning ICredentials
-        """
-
-
-class IAuthenticatedRequest(Interface):
-    """
-    A request that has been authenticated with the use of Cred,
-    and holds a reference to the avatar returned by portal.login
-    """
-
-    avatarInterface = Attribute(("The credential interface implemented by "
-                                 "the avatar"))
-
-    avatar = Attribute("The application specific avatar returned by "
-                       "the application's realm")
-
-
-class IHTTPUser(Interface):
-    """
-    A generic interface that can implemented by an avatar to provide
-    access to the username used when authenticating.
-    """
-
-    username = Attribute(("A string representing the username portion of "
-                          "the credentials used for authentication"))

Copied: CalendarServer/trunk/twext/web2/auth/interfaces.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/auth/interfaces.py)
===================================================================
--- CalendarServer/trunk/twext/web2/auth/interfaces.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/auth/interfaces.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,83 @@
+##
+# Copyright (c) 2004-2007 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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 zope.interface import Interface, Attribute
+
+class ICredentialFactory(Interface):
+    """
+    A credential factory provides state between stages in HTTP
+    authentication.  It is ultimately in charge of creating an
+    ICredential for the specified scheme, that will be used by
+    cred to complete authentication.
+    """
+    scheme = Attribute(("string indicating the authentication scheme "
+                        "this factory is associated with."))
+
+    def getChallenge(peer):
+        """
+        Generate a challenge the client may respond to.
+
+        @type peer: L{twisted.internet.interfaces.IAddress}
+        @param peer: The client's address
+
+        @rtype: C{dict}
+        @return: Deferred returning dictionary of challenge arguments
+        """
+
+    def decode(response, request):
+        """
+        Create a credentials object from the given response.
+        May raise twisted.cred.error.LoginFailed if the response is invalid.
+
+        @type response: C{str}
+        @param response: scheme specific response string
+
+        @type request: L{twext.web2.server.Request}
+        @param request: the request being processed
+
+        @return: Deferred returning ICredentials
+        """
+
+
+class IAuthenticatedRequest(Interface):
+    """
+    A request that has been authenticated with the use of Cred,
+    and holds a reference to the avatar returned by portal.login
+    """
+
+    avatarInterface = Attribute(("The credential interface implemented by "
+                                 "the avatar"))
+
+    avatar = Attribute("The application specific avatar returned by "
+                       "the application's realm")
+
+
+class IHTTPUser(Interface):
+    """
+    A generic interface that can implemented by an avatar to provide
+    access to the username used when authenticating.
+    """
+
+    username = Attribute(("A string representing the username portion of "
+                          "the credentials used for authentication"))

Deleted: CalendarServer/trunk/twext/web2/auth/wrapper.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/auth/wrapper.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/auth/wrapper.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,253 +0,0 @@
-# -*- test-case-name: twext.web2.test.test_httpauth -*-
-##
-# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
-# Copyright (c) 2010 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.
-#
-##
-
-"""
-Wrapper Resources for rfc2617 HTTP Auth.
-"""
-from zope.interface import implements, directlyProvides
-from twisted.cred import error, credentials
-from twisted.internet.defer import gatherResults, succeed
-from twisted.python import failure
-from twext.web2 import responsecode
-from twext.web2 import http
-from twext.web2 import iweb
-from twext.web2.auth.interfaces import IAuthenticatedRequest
-
-class UnauthorizedResponse(http.StatusResponse):
-    """A specialized response class for generating www-authenticate headers
-    from the given L{CredentialFactory} instances
-    """
-
-    def __init__(self):
-        super(UnauthorizedResponse, self).__init__(
-            responsecode.UNAUTHORIZED,
-            "You are not authorized to access this resource.")
-
-    def _generateHeaders(self, factories, remoteAddr=None):
-        """
-        Set up the response's headers.
-
-        @param factories: A L{dict} of {'scheme': ICredentialFactory}
-        @param remoteAddr: An L{IAddress} for the connecting client.
-        """
-        schemes = []
-        challengeDs = []
-        for factory in factories.itervalues():
-            schemes.append(factory.scheme)
-            challengeDs.append(factory.getChallenge(remoteAddr))
-        def _setAuthHeader(challenges):
-            authHeaders = zip(schemes, challenges)
-            self.headers.setHeader('www-authenticate', authHeaders)
-        return gatherResults(challengeDs).addCallback(_setAuthHeader)
-
-
-    @classmethod
-    def makeResponse(cls, factories, remoteAddr=None):
-        """
-        Create an Unauthorized response.
-
-        @param factories: A L{dict} of {'scheme': ICredentialFactory}
-        @param remoteAddr: An L{IAddress} for the connecting client.
-
-        @return: a Deferred that fires with the L{UnauthorizedResponse}
-        instance.
-        """
-        response = UnauthorizedResponse()
-        d = response._generateHeaders(factories, remoteAddr)
-        d.addCallback(lambda _:response)
-        return d
-
-
-
-class HTTPAuthResource(object):
-    """I wrap a resource to prevent it being accessed unless the authentication
-       can be completed using the credential factory, portal, and interfaces
-       specified.
-    """
-
-    implements(iweb.IResource)
-
-    def __init__(self, wrappedResource, credentialFactories,
-                 portal, interfaces):
-        """
-        @param wrappedResource: A L{twext.web2.iweb.IResource} to be returned
-                                from locateChild and render upon successful
-                                authentication.
-
-        @param credentialFactories: A list of instances that implement
-                                    L{ICredentialFactory}.
-        @type credentialFactories: L{list}
-
-        @param portal: Portal to handle logins for this resource.
-        @type portal: L{twisted.cred.portal.Portal}
-
-        @param interfaces: the interfaces that are allowed to log in via the
-                           given portal
-        @type interfaces: L{tuple}
-        """
-
-        self.wrappedResource = wrappedResource
-
-        self.credentialFactories = dict([(factory.scheme, factory)
-                                         for factory in credentialFactories])
-        self.portal = portal
-        self.interfaces = interfaces
-
-    def _loginSucceeded(self, avatar, request):
-        """
-        Callback for successful login.
-
-        @param avatar: A tuple of the form (interface, avatar) as
-            returned by your realm.
-
-        @param request: L{IRequest} that encapsulates this auth
-            attempt.
-
-        @return: the IResource in C{self.wrappedResource}
-        """
-        request.avatarInterface, request.avatar = avatar
-
-        directlyProvides(request, IAuthenticatedRequest)
-
-        def _addAuthenticateHeaders(request, response):
-            """
-            A response filter that adds www-authenticate headers
-            to an outgoing response if it's code is UNAUTHORIZED (401)
-            and it does not already have them.
-            """
-            if response.code == responsecode.UNAUTHORIZED:
-                if not response.headers.hasHeader('www-authenticate'):
-                    d = UnauthorizedResponse.makeResponse(
-                        self.credentialFactories,
-                        request.remoteAddr)
-                    def _respond(newResp):
-                        response.headers.setHeader(
-                            'www-authenticate',
-                            newResp.headers.getHeader('www-authenticate'))
-                        return response
-                    d.addCallback(_respond)
-                    return d
-
-            return succeed(response)
-
-        _addAuthenticateHeaders.handleErrors = True
-
-        request.addResponseFilter(_addAuthenticateHeaders)
-
-        return self.wrappedResource
-
-
-    def _loginFailed(self, ignored, request):
-        """
-        Errback for failed login.
-
-
-        @param request: L{IRequest} that encapsulates this auth
-            attempt.
-
-        @return: A Deferred L{Failure} containing an L{HTTPError} containing the
-            L{UnauthorizedResponse} if C{result} is an L{UnauthorizedLogin}
-            or L{UnhandledCredentials} error
-        """
-        d = UnauthorizedResponse.makeResponse(self.credentialFactories,
-                                              request.remoteAddr)
-
-        def _fail(response):
-            return failure.Failure(http.HTTPError(response))
-        return d.addCallback(_fail)
-
-
-    def login(self, factory, response, request):
-        """
-        @param factory: An L{ICredentialFactory} that understands the given
-            response.
-
-        @param response: The client's authentication response as a string.
-
-        @param request: The request that prompted this authentication attempt.
-
-        @return: A L{Deferred} that fires with the wrappedResource on success
-            or a failure containing an L{UnauthorizedResponse}
-        """
-        d = factory.decode(response, request)
-        def _decodeFailure(err):
-            err.trap(error.LoginFailed)
-            d = UnauthorizedResponse.makeResponse(self.credentialFactories,
-                                                  request.remoteAddr)
-            def _respond(response):
-                return failure.Failure(http.HTTPError(response))
-            return d.addCallback(_respond)
-        def _login(creds):
-            return self.portal.login(creds, None, *self.interfaces
-                                     ).addCallbacks(self._loginSucceeded,
-                                                    self._loginFailed,
-                                                    (request,), None,
-                                                    (request,), None)
-        return d.addErrback(_decodeFailure).addCallback(_login)
-
-
-    def authenticate(self, request):
-        """
-        Attempt to authenticate the given 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)
-
-        elif authHeader[0] not in self.credentialFactories:
-            return self._loginFailed(None, request)
-        else:
-            return self.login(self.credentialFactories[authHeader[0]],
-                              authHeader[1], request)
-
-
-    def locateChild(self, request, seg):
-        """
-        Authenticate the request then return the C{self.wrappedResource}
-        and the unmodified segments.
-        """
-        return self.authenticate(request), seg
-
-    def renderHTTP(self, request):
-        """
-        Authenticate the request then return the result of calling renderHTTP
-        on C{self.wrappedResource}
-        """
-        def _renderResource(resource):
-            return resource.renderHTTP(request)
-
-        d = self.authenticate(request)
-        d.addCallback(_renderResource)
-
-        return d

Copied: CalendarServer/trunk/twext/web2/auth/wrapper.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/auth/wrapper.py)
===================================================================
--- CalendarServer/trunk/twext/web2/auth/wrapper.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/auth/wrapper.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,253 @@
+# -*- test-case-name: twext.web2.test.test_httpauth -*-
+##
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.
+#
+##
+
+"""
+Wrapper Resources for rfc2617 HTTP Auth.
+"""
+from zope.interface import implements, directlyProvides
+from twisted.cred import error, credentials
+from twisted.internet.defer import gatherResults, succeed
+from twisted.python import failure
+from twext.web2 import responsecode
+from twext.web2 import http
+from twext.web2 import iweb
+from twext.web2.auth.interfaces import IAuthenticatedRequest
+
+class UnauthorizedResponse(http.StatusResponse):
+    """A specialized response class for generating www-authenticate headers
+    from the given L{CredentialFactory} instances
+    """
+
+    def __init__(self):
+        super(UnauthorizedResponse, self).__init__(
+            responsecode.UNAUTHORIZED,
+            "You are not authorized to access this resource.")
+
+    def _generateHeaders(self, factories, remoteAddr=None):
+        """
+        Set up the response's headers.
+
+        @param factories: A L{dict} of {'scheme': ICredentialFactory}
+        @param remoteAddr: An L{IAddress} for the connecting client.
+        """
+        schemes = []
+        challengeDs = []
+        for factory in factories.itervalues():
+            schemes.append(factory.scheme)
+            challengeDs.append(factory.getChallenge(remoteAddr))
+        def _setAuthHeader(challenges):
+            authHeaders = zip(schemes, challenges)
+            self.headers.setHeader('www-authenticate', authHeaders)
+        return gatherResults(challengeDs).addCallback(_setAuthHeader)
+
+
+    @classmethod
+    def makeResponse(cls, factories, remoteAddr=None):
+        """
+        Create an Unauthorized response.
+
+        @param factories: A L{dict} of {'scheme': ICredentialFactory}
+        @param remoteAddr: An L{IAddress} for the connecting client.
+
+        @return: a Deferred that fires with the L{UnauthorizedResponse}
+        instance.
+        """
+        response = UnauthorizedResponse()
+        d = response._generateHeaders(factories, remoteAddr)
+        d.addCallback(lambda _:response)
+        return d
+
+
+
+class HTTPAuthResource(object):
+    """I wrap a resource to prevent it being accessed unless the authentication
+       can be completed using the credential factory, portal, and interfaces
+       specified.
+    """
+
+    implements(iweb.IResource)
+
+    def __init__(self, wrappedResource, credentialFactories,
+                 portal, interfaces):
+        """
+        @param wrappedResource: A L{twext.web2.iweb.IResource} to be returned
+                                from locateChild and render upon successful
+                                authentication.
+
+        @param credentialFactories: A list of instances that implement
+                                    L{ICredentialFactory}.
+        @type credentialFactories: L{list}
+
+        @param portal: Portal to handle logins for this resource.
+        @type portal: L{twisted.cred.portal.Portal}
+
+        @param interfaces: the interfaces that are allowed to log in via the
+                           given portal
+        @type interfaces: L{tuple}
+        """
+
+        self.wrappedResource = wrappedResource
+
+        self.credentialFactories = dict([(factory.scheme, factory)
+                                         for factory in credentialFactories])
+        self.portal = portal
+        self.interfaces = interfaces
+
+    def _loginSucceeded(self, avatar, request):
+        """
+        Callback for successful login.
+
+        @param avatar: A tuple of the form (interface, avatar) as
+            returned by your realm.
+
+        @param request: L{IRequest} that encapsulates this auth
+            attempt.
+
+        @return: the IResource in C{self.wrappedResource}
+        """
+        request.avatarInterface, request.avatar = avatar
+
+        directlyProvides(request, IAuthenticatedRequest)
+
+        def _addAuthenticateHeaders(request, response):
+            """
+            A response filter that adds www-authenticate headers
+            to an outgoing response if it's code is UNAUTHORIZED (401)
+            and it does not already have them.
+            """
+            if response.code == responsecode.UNAUTHORIZED:
+                if not response.headers.hasHeader('www-authenticate'):
+                    d = UnauthorizedResponse.makeResponse(
+                        self.credentialFactories,
+                        request.remoteAddr)
+                    def _respond(newResp):
+                        response.headers.setHeader(
+                            'www-authenticate',
+                            newResp.headers.getHeader('www-authenticate'))
+                        return response
+                    d.addCallback(_respond)
+                    return d
+
+            return succeed(response)
+
+        _addAuthenticateHeaders.handleErrors = True
+
+        request.addResponseFilter(_addAuthenticateHeaders)
+
+        return self.wrappedResource
+
+
+    def _loginFailed(self, ignored, request):
+        """
+        Errback for failed login.
+
+
+        @param request: L{IRequest} that encapsulates this auth
+            attempt.
+
+        @return: A Deferred L{Failure} containing an L{HTTPError} containing the
+            L{UnauthorizedResponse} if C{result} is an L{UnauthorizedLogin}
+            or L{UnhandledCredentials} error
+        """
+        d = UnauthorizedResponse.makeResponse(self.credentialFactories,
+                                              request.remoteAddr)
+
+        def _fail(response):
+            return failure.Failure(http.HTTPError(response))
+        return d.addCallback(_fail)
+
+
+    def login(self, factory, response, request):
+        """
+        @param factory: An L{ICredentialFactory} that understands the given
+            response.
+
+        @param response: The client's authentication response as a string.
+
+        @param request: The request that prompted this authentication attempt.
+
+        @return: A L{Deferred} that fires with the wrappedResource on success
+            or a failure containing an L{UnauthorizedResponse}
+        """
+        d = factory.decode(response, request)
+        def _decodeFailure(err):
+            err.trap(error.LoginFailed)
+            d = UnauthorizedResponse.makeResponse(self.credentialFactories,
+                                                  request.remoteAddr)
+            def _respond(response):
+                return failure.Failure(http.HTTPError(response))
+            return d.addCallback(_respond)
+        def _login(creds):
+            return self.portal.login(creds, None, *self.interfaces
+                                     ).addCallbacks(self._loginSucceeded,
+                                                    self._loginFailed,
+                                                    (request,), None,
+                                                    (request,), None)
+        return d.addErrback(_decodeFailure).addCallback(_login)
+
+
+    def authenticate(self, request):
+        """
+        Attempt to authenticate the given 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)
+
+        elif authHeader[0] not in self.credentialFactories:
+            return self._loginFailed(None, request)
+        else:
+            return self.login(self.credentialFactories[authHeader[0]],
+                              authHeader[1], request)
+
+
+    def locateChild(self, request, seg):
+        """
+        Authenticate the request then return the C{self.wrappedResource}
+        and the unmodified segments.
+        """
+        return self.authenticate(request), seg
+
+    def renderHTTP(self, request):
+        """
+        Authenticate the request then return the result of calling renderHTTP
+        on C{self.wrappedResource}
+        """
+        def _renderResource(resource):
+            return resource.renderHTTP(request)
+
+        d = self.authenticate(request)
+        d.addCallback(_renderResource)
+
+        return d

Modified: CalendarServer/trunk/twext/web2/channel/__init__.py
===================================================================
--- CalendarServer/trunk/twext/web2/channel/__init__.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/channel/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,19 +1,32 @@
+# -*- test-case-name: twext.web2.test.test_cgi,twext.web2.test.test_http -*-
 ##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# Copyright (c) 2010 Apple Computer, 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
+# 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:
 #
-# http://www.apache.org/licenses/LICENSE-2.0
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
 #
-# 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.
+# 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.
+#
 ##
 
 """
-Extentions to twisted.web2.channel
+Various backend channel implementations for web2.
 """
+
+from twext.web2.channel.http import HTTPFactory
+
+__all__ = ['HTTPFactory']

Modified: CalendarServer/trunk/twext/web2/channel/http.py
===================================================================
--- CalendarServer/trunk/twext/web2/channel/http.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/channel/http.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,37 +1,48 @@
+# -*- test-case-name: twext.web2.test.test_http -*-
 ##
-# Copyright (c) 2008-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# Copyright (c) 2008-2010 Apple Computer, 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
+# 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:
 #
-# http://www.apache.org/licenses/LICENSE-2.0
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
 #
-# 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.
+# 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.
+#
 ##
 
+import warnings
+import socket
+from cStringIO import StringIO
+from zope.interface import implements
+
+from twisted.python import log
+from twisted.internet import interfaces, protocol, reactor
+from twisted.protocols import policies, basic
+from twext.web2 import responsecode
+from twext.web2 import http_headers
+from twext.web2 import http
+
 from random import randint
 
-from twisted.internet import protocol
-from twisted.python import log
-from twisted.web2.channel.http import HTTPFactory, HTTPChannelRequest,\
-    HTTPChannel
-from twisted.web2.http import Request, RedirectResponse
+from twext.web2.http import Request, RedirectResponse
 from twistedcaldav.config import config
 
 from twistedcaldav import accounting
 import time
 
-__all__ = [
-    "HTTP503LoggingFactory",
-    "LimitingHTTPFactory",
-    "SSLRedirectRequest",
-]
-
 class OverloadedLoggingServerProtocol (protocol.Protocol):
     def __init__(self, retryAfter, outstandingRequests):
         self.retryAfter = retryAfter
@@ -59,6 +70,908 @@
         )
         self.transport.loseConnection()
 
+class SSLRedirectRequest(Request):
+    """ For redirecting HTTP to HTTPS port """
+
+    def process(self):
+        if config.SSLPort == 443:
+            location = (
+                "https://%s%s"
+                % (config.ServerHostName, self.uri)
+            )
+        else:
+            location = (
+                "https://%s:%d%s"
+                % (config.ServerHostName, config.SSLPort, self.uri)
+            )
+        self.writeResponse(RedirectResponse(location))
+
+# >%
+
+PERSIST_NO_PIPELINE, PERSIST_PIPELINE = (1,2)
+
+_cachedHostNames = {}
+def _cachedGetHostByAddr(hostaddr):
+    hostname = _cachedHostNames.get(hostaddr)
+    if hostname is None:
+        try:
+            hostname = socket.gethostbyaddr(hostaddr)[0]
+        except socket.herror:
+            hostname = hostaddr
+        _cachedHostNames[hostaddr]=hostname
+    return hostname
+
+class StringTransport(object):
+    """
+    I am a StringIO wrapper that conforms for the transport API. I support
+    the 'writeSequence' method.
+    """
+    def __init__(self):
+        self.s = StringIO()
+    def writeSequence(self, seq):
+        self.s.write(''.join(seq))
+    def __getattr__(self, attr):
+        return getattr(self.__dict__['s'], attr)
+
+class AbortedException(Exception):
+    pass
+
+
+class HTTPParser(object):
+    """This class handles the parsing side of HTTP processing. With a suitable
+    subclass, it can parse either the client side or the server side of the
+    connection.
+    """
+    
+    # Class config:
+    parseCloseAsEnd = False
+    
+    # Instance vars
+    chunkedIn = False
+    headerlen = 0
+    length = None
+    inHeaders = None
+    partialHeader = ''
+    connHeaders = None
+    finishedReading = False
+
+    channel = None
+
+    # For subclassing...
+    # Needs attributes:
+    #  version
+
+    # Needs functions:
+    #  createRequest()
+    #  processRequest()
+    #  _abortWithError()
+    #  handleContentChunk(data)
+    #  handleContentComplete()
+
+    # Needs functions to exist on .channel
+    #  channel.maxHeaderLength
+    #  channel.requestReadFinished(self)
+    #  channel.setReadPersistent(self, persistent)
+    # (from LineReceiver):
+    #  channel.setRawMode()
+    #  channel.setLineMode(extraneous)
+    #  channel.pauseProducing()
+    #  channel.resumeProducing()
+    #  channel.stopProducing()
+    
+    
+    def __init__(self, channel):
+        self.inHeaders = http_headers.Headers()
+        self.channel = channel
+        
+    def lineReceived(self, line):
+        if self.chunkedIn:
+            # Parsing a chunked input
+            if self.chunkedIn == 1:
+                # First we get a line like "chunk-size [';' chunk-extension]"
+                # (where chunk extension is just random crap as far as we're concerned)
+                # RFC says to ignore any extensions you don't recognize -- that's all of them.
+                chunksize = line.split(';', 1)[0]
+                try:
+                    self.length = int(chunksize, 16)
+                except:
+                    self._abortWithError(responsecode.BAD_REQUEST, "Invalid chunk size, not a hex number: %s!" % chunksize)
+                if self.length < 0:
+                    self._abortWithError(responsecode.BAD_REQUEST, "Invalid chunk size, negative.")
+
+                if self.length == 0:
+                    # We're done, parse the trailers line
+                    self.chunkedIn = 3
+                else:
+                    # Read self.length bytes of raw data
+                    self.channel.setRawMode()
+            elif self.chunkedIn == 2:
+                # After we got data bytes of the appropriate length, we end up here,
+                # waiting for the CRLF, then go back to get the next chunk size.
+                if line != '':
+                    self._abortWithError(responsecode.BAD_REQUEST, "Excess %d bytes sent in chunk transfer mode" % len(line))
+                self.chunkedIn = 1
+            elif self.chunkedIn == 3:
+                # TODO: support Trailers (maybe! but maybe not!)
+                
+                # After getting the final "0" chunk we're here, and we *EAT MERCILESSLY*
+                # any trailer headers sent, and wait for the blank line to terminate the
+                # request.
+                if line == '':
+                    self.allContentReceived()
+        # END of chunk handling
+        elif line == '':
+            # Empty line => End of headers
+            if self.partialHeader:
+                self.headerReceived(self.partialHeader)
+            self.partialHeader = ''
+            self.allHeadersReceived()    # can set chunkedIn
+            self.createRequest()
+            if self.chunkedIn:
+                # stay in linemode waiting for chunk header
+                pass
+            elif self.length == 0:
+                # no content expected
+                self.allContentReceived()
+            else:
+                # await raw data as content
+                self.channel.setRawMode()
+                # Should I do self.pauseProducing() here?
+            self.processRequest()
+        else:
+            self.headerlen += len(line)
+            if self.headerlen > self.channel.maxHeaderLength:
+                self._abortWithError(responsecode.BAD_REQUEST, 'Headers too long.')
+            
+            if line[0] in ' \t':
+                # Append a header continuation
+                self.partialHeader += line
+            else:
+                if self.partialHeader:
+                    self.headerReceived(self.partialHeader)
+                self.partialHeader = line
+
+    def rawDataReceived(self, data):
+        """Handle incoming content."""
+        datalen = len(data)
+        if datalen < self.length:
+            self.handleContentChunk(data)
+            self.length = self.length - datalen
+        else:
+            self.handleContentChunk(data[:self.length])
+            extraneous = data[self.length:]
+            channel = self.channel # could go away from allContentReceived.
+            if not self.chunkedIn:
+                self.allContentReceived()
+            else:
+                # NOTE: in chunked mode, self.length is the size of the current chunk,
+                # so we still have more to read.
+                self.chunkedIn = 2 # Read next chunksize
+            
+            channel.setLineMode(extraneous)
+
+    def headerReceived(self, line):
+        """Store this header away. Check for too much header data
+           (> channel.maxHeaderLength) and abort the connection if so.
+        """
+        nameval = line.split(':', 1)
+        if len(nameval) != 2:
+            self._abortWithError(responsecode.BAD_REQUEST, "No ':' in header.")
+        
+        name, val = nameval
+        val = val.lstrip(' \t')
+        self.inHeaders.addRawHeader(name, val)
+        
+
+    def allHeadersReceived(self):
+        # Split off connection-related headers
+        connHeaders = self.splitConnectionHeaders()
+
+        # Set connection parameters from headers
+        self.setConnectionParams(connHeaders)
+        self.connHeaders = connHeaders
+        
+    def allContentReceived(self):
+        self.finishedReading = True
+        self.channel.requestReadFinished(self)
+        self.handleContentComplete()
+        
+        
+    def splitConnectionHeaders(self):
+        """
+        Split off connection control headers from normal headers.
+
+        The normal headers are then passed on to user-level code, while the
+        connection headers are stashed in .connHeaders and used for things like
+        request/response framing.
+
+        This corresponds roughly with the HTTP RFC's description of 'hop-by-hop'
+        vs 'end-to-end' headers in RFC2616 S13.5.1, with the following
+        exceptions:
+
+         - proxy-authenticate and proxy-authorization are not treated as
+           connection headers.
+
+         - content-length is, as it is intimately related with low-level HTTP
+           parsing, and is made available to user-level code via the stream
+           length, rather than a header value. (except for HEAD responses, in
+           which case it is NOT used by low-level HTTP parsing, and IS kept in
+           the normal headers.
+        """
+
+        def move(name):
+            h = inHeaders.getRawHeaders(name, None)
+            if h is not None:
+                inHeaders.removeHeader(name)
+                connHeaders.setRawHeaders(name, h)
+
+        # NOTE: According to HTTP spec, we're supposed to eat the
+        # 'Proxy-Authenticate' and 'Proxy-Authorization' headers also, but that
+        # doesn't sound like a good idea to me, because it makes it impossible
+        # to have a non-authenticating transparent proxy in front of an
+        # authenticating proxy. An authenticating proxy can eat them itself.
+        #
+        # 'Proxy-Connection' is an undocumented HTTP 1.0 abomination.
+        connHeaderNames = ['content-length', 'connection', 'keep-alive', 'te',
+                           'trailers', 'transfer-encoding', 'upgrade',
+                           'proxy-connection']
+        inHeaders = self.inHeaders
+        connHeaders = http_headers.Headers()
+
+        move('connection')
+        if self.version < (1,1):
+            # Remove all headers mentioned in Connection, because a HTTP 1.0
+            # proxy might have erroneously forwarded it from a 1.1 client.
+            for name in connHeaders.getHeader('connection', ()):
+                if inHeaders.hasHeader(name):
+                    inHeaders.removeHeader(name)
+        else:
+            # Otherwise, just add the headers listed to the list of those to move
+            connHeaderNames.extend(connHeaders.getHeader('connection', ()))
+
+        # If the request was HEAD, self.length has been set to 0 by
+        # HTTPClientRequest.submit; in this case, Content-Length should
+        # be treated as a response header, not a connection header.
+
+        # Note: this assumes the invariant that .length will always be None
+        # coming into this function, unless this is a HEAD request.
+        if self.length is not None:
+            connHeaderNames.remove('content-length')
+
+        for headername in connHeaderNames:
+            move(headername)
+
+        return connHeaders
+
+    def setConnectionParams(self, connHeaders):
+        # Figure out persistent connection stuff
+        if self.version >= (1,1):
+            if 'close' in connHeaders.getHeader('connection', ()):
+                readPersistent = False
+            else:
+                readPersistent = PERSIST_PIPELINE
+        elif 'keep-alive' in connHeaders.getHeader('connection', ()):
+            readPersistent = PERSIST_NO_PIPELINE
+        else:
+            readPersistent = False
+
+
+        # Okay, now implement section 4.4 Message Length to determine
+        # how to find the end of the incoming HTTP message.
+        transferEncoding = connHeaders.getHeader('transfer-encoding')
+        
+        if transferEncoding:
+            if transferEncoding[-1] == 'chunked':
+                # Chunked
+                self.chunkedIn = 1
+                # Cut off the chunked encoding (cause it's special)
+                transferEncoding = transferEncoding[:-1]
+            elif not self.parseCloseAsEnd:
+                # Would close on end of connection, except this can't happen for
+                # client->server data. (Well..it could actually, since TCP has half-close
+                # but the HTTP spec says it can't, so we'll pretend it's right.)
+                self._abortWithError(responsecode.BAD_REQUEST, "Transfer-Encoding received without chunked in last position.")
+            
+            # TODO: support gzip/etc encodings.
+            # FOR NOW: report an error if the client uses any encodings.
+            # They shouldn't, because we didn't send a TE: header saying it's okay.
+            if transferEncoding:
+                self._abortWithError(responsecode.NOT_IMPLEMENTED, "Transfer-Encoding %s not supported." % transferEncoding)
+        else:
+            # No transfer-coding.
+            self.chunkedIn = 0
+            if self.parseCloseAsEnd:
+                # If no Content-Length, then it's indeterminate length data
+                # (unless the responsecode was one of the special no body ones)
+                # Also note that for HEAD requests, connHeaders won't have
+                # content-length even if the response did.
+                if self.code in http.NO_BODY_CODES:
+                    self.length = 0
+                else:
+                    self.length = connHeaders.getHeader('content-length', self.length)
+
+                # If it's an indeterminate stream without transfer encoding, it must be
+                # the last request.
+                if self.length is None:
+                    readPersistent = False
+            else:
+                # If no Content-Length either, assume no content.
+                self.length = connHeaders.getHeader('content-length', 0)
+
+        # Set the calculated persistence
+        self.channel.setReadPersistent(readPersistent)
+        
+    def abortParse(self):
+        # If we're erroring out while still reading the request
+        if not self.finishedReading:
+            self.finishedReading = True
+            self.channel.setReadPersistent(False)
+            self.channel.requestReadFinished(self)
+        
+    # producer interface
+    def pauseProducing(self):
+        if not self.finishedReading:
+            self.channel.pauseProducing()
+        
+    def resumeProducing(self):
+        if not self.finishedReading:
+            self.channel.resumeProducing()
+       
+    def stopProducing(self):
+        if not self.finishedReading:
+            self.channel.stopProducing()
+
+class HTTPChannelRequest(HTTPParser):
+    """This class handles the state and parsing for one HTTP request.
+    It is responsible for all the low-level connection oriented behavior.
+    Thus, it takes care of keep-alive, de-chunking, etc., and passes
+    the non-connection headers on to the user-level Request object."""
+    
+    command = path = version = None
+    queued = 0
+    request = None
+    
+    out_version = "HTTP/1.1"
+    
+    def __init__(self, channel, queued=0):
+        HTTPParser.__init__(self, channel)
+        self.queued=queued
+
+        # Buffer writes to a string until we're first in line
+        # to write a response
+        if queued:
+            self.transport = StringTransport()
+        else:
+            self.transport = self.channel.transport
+        
+        # set the version to a fallback for error generation
+        self.version = (1,0)
+
+
+    def gotInitialLine(self, initialLine):
+        parts = initialLine.split()
+        
+        # Parse the initial request line
+        if len(parts) != 3:
+            if len(parts) == 1:
+                parts.append('/')
+            if len(parts) == 2 and parts[1][0] == '/':
+                parts.append('HTTP/0.9')
+            else:
+                self._abortWithError(responsecode.BAD_REQUEST, 'Bad request line: %s' % initialLine)
+
+        self.command, self.path, strversion = parts
+        try:
+            protovers = http.parseVersion(strversion)
+            if protovers[0] != 'http':
+                raise ValueError()
+        except ValueError:
+            self._abortWithError(responsecode.BAD_REQUEST, "Unknown protocol: %s" % strversion)
+        
+        self.version = protovers[1:3]
+        
+        # Ensure HTTP 0 or HTTP 1.
+        if self.version[0] > 1:
+            self._abortWithError(responsecode.HTTP_VERSION_NOT_SUPPORTED, 'Only HTTP 0.9 and HTTP 1.x are supported.')
+
+        if self.version[0] == 0:
+            # simulate end of headers, as HTTP 0 doesn't have headers.
+            self.lineReceived('')
+
+    def lineLengthExceeded(self, line, wasFirst=False):
+        code = wasFirst and responsecode.REQUEST_URI_TOO_LONG or responsecode.BAD_REQUEST
+        self._abortWithError(code, 'Header line too long.')
+
+    def createRequest(self):
+        self.request = self.channel.requestFactory(self, self.command, self.path, self.version, self.length, self.inHeaders)
+        del self.inHeaders
+
+    def processRequest(self):
+        self.request.process()
+        
+    def handleContentChunk(self, data):
+        self.request.handleContentChunk(data)
+        
+    def handleContentComplete(self):
+        self.request.handleContentComplete()
+        
+############## HTTPChannelRequest *RESPONSE* methods #############
+    producer = None
+    chunkedOut = False
+    finished = False
+    
+    ##### Request Callbacks #####
+    def writeIntermediateResponse(self, code, headers=None):
+        if self.version >= (1,1):
+            self._writeHeaders(code, headers, False)
+
+    def writeHeaders(self, code, headers):
+        self._writeHeaders(code, headers, True)
+        
+    def _writeHeaders(self, code, headers, addConnectionHeaders):
+        # HTTP 0.9 doesn't have headers.
+        if self.version[0] == 0:
+            return
+        
+        l = []
+        code_message = responsecode.RESPONSES.get(code, "Unknown Status")
+        
+        l.append('%s %s %s\r\n' % (self.out_version, code,
+                                   code_message))
+        if headers is not None:
+            for name, valuelist in headers.getAllRawHeaders():
+                for value in valuelist:
+                    l.append("%s: %s\r\n" % (name, value))
+
+        if addConnectionHeaders:
+            # if we don't have a content length, we send data in
+            # chunked mode, so that we can support persistent connections.
+            if (headers.getHeader('content-length') is None and
+                self.command != "HEAD" and code not in http.NO_BODY_CODES):
+                if self.version >= (1,1):
+                    l.append("%s: %s\r\n" % ('Transfer-Encoding', 'chunked'))
+                    self.chunkedOut = True
+                else:
+                    # Cannot use persistent connections if we can't do chunking
+                    self.channel.dropQueuedRequests()
+            
+            if self.channel.isLastRequest(self):
+                l.append("%s: %s\r\n" % ('Connection', 'close'))
+            elif self.version < (1,1):
+                l.append("%s: %s\r\n" % ('Connection', 'Keep-Alive'))
+        
+        l.append("\r\n")
+        self.transport.writeSequence(l)
+        
+    
+    def write(self, data):
+        if not data:
+            return
+        elif self.chunkedOut:
+            self.transport.writeSequence(("%X\r\n" % len(data), data, "\r\n"))
+        else:
+            self.transport.write(data)
+        
+    def finish(self):
+        """We are finished writing data."""
+        if self.finished:
+            warnings.warn("Warning! request.finish called twice.", stacklevel=2)
+            return
+        
+        if self.chunkedOut:
+            # write last chunk and closing CRLF
+            self.transport.write("0\r\n\r\n")
+        
+        self.finished = True
+        if not self.queued:
+            self._cleanup()
+
+
+    def abortConnection(self, closeWrite=True):
+        """Abort the HTTP connection because of some kind of unrecoverable
+        error. If closeWrite=False, then only abort reading, but leave
+        the writing side alone. This is mostly for internal use by
+        the HTTP request parsing logic, so that it can call an error
+        page generator.
+        
+        Otherwise, completely shut down the connection.
+        """
+        self.abortParse()
+        if closeWrite:
+            if self.producer:
+                self.producer.stopProducing()
+                self.unregisterProducer()
+            
+            self.finished = True
+            if self.queued:
+                self.transport.reset()
+                self.transport.truncate()
+            else:
+                self._cleanup()
+
+    def getHostInfo(self):
+        t=self.channel.transport
+        secure = interfaces.ISSLTransport(t, None) is not None
+        host = t.getHost()
+        host.host = _cachedGetHostByAddr(host.host)
+        return host, secure
+
+    def getRemoteHost(self):
+        return self.channel.transport.getPeer()
+    
+    ##### End Request Callbacks #####
+
+    def _abortWithError(self, errorcode, text=''):
+        """Handle low level protocol errors."""
+        headers = http_headers.Headers()
+        headers.setHeader('content-length', len(text)+1)
+        
+        self.abortConnection(closeWrite=False)
+        self.writeHeaders(errorcode, headers)
+        self.write(text)
+        self.write("\n")
+        self.finish()
+        raise AbortedException
+    
+    def _cleanup(self):
+        """Called when have finished responding and are no longer queued."""
+        if self.producer:
+            log.err(RuntimeError("Producer was not unregistered for %s" % self))
+            self.unregisterProducer()
+        self.channel.requestWriteFinished(self)
+        del self.transport
+        
+    # methods for channel - end users should not use these
+
+    def noLongerQueued(self):
+        """Notify the object that it is no longer queued.
+
+        We start writing whatever data we have to the transport, etc.
+
+        This method is not intended for users.
+        """
+        if not self.queued:
+            raise RuntimeError, "noLongerQueued() got called unnecessarily."
+
+        self.queued = 0
+
+        # set transport to real one and send any buffer data
+        data = self.transport.getvalue()
+        self.transport = self.channel.transport
+        if data:
+            self.transport.write(data)
+
+        # if we have producer, register it with transport
+        if (self.producer is not None) and not self.finished:
+            self.transport.registerProducer(self.producer, True)
+
+        # if we're finished, clean up
+        if self.finished:
+            self._cleanup()
+
+
+    # consumer interface
+    def registerProducer(self, producer, streaming):
+        """Register a producer.
+        """
+        
+        if self.producer:
+            raise ValueError, "registering producer %s before previous one (%s) was unregistered" % (producer, self.producer)
+        
+        self.producer = producer
+        
+        if self.queued:
+            producer.pauseProducing()
+        else:
+            self.transport.registerProducer(producer, streaming)
+
+    def unregisterProducer(self):
+        """Unregister the producer."""
+        if not self.queued:
+            self.transport.unregisterProducer()
+        self.producer = None
+
+    def connectionLost(self, reason):
+        """connection was lost"""
+        if self.queued and self.producer:
+            self.producer.stopProducing()
+            self.producer = None
+        if self.request:
+            self.request.connectionLost(reason)
+    
+class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin, object):
+    """A receiver for HTTP requests. Handles splitting up the connection
+    for the multiple HTTPChannelRequests that may be in progress on this
+    channel.
+
+    @ivar timeOut: number of seconds to wait before terminating an
+    idle connection.
+
+    @ivar maxPipeline: number of outstanding in-progress requests
+    to allow before pausing the input.
+
+    @ivar maxHeaderLength: number of bytes of header to accept from
+    the client.
+
+    """
+    
+    implements(interfaces.IHalfCloseableProtocol)
+    
+    ## Configuration parameters. Set in instances or subclasses.
+    
+    # How many simultaneous requests to handle.
+    maxPipeline = 4
+
+    # Timeout when between two requests
+    betweenRequestsTimeOut = 15
+    # Timeout between lines or bytes while reading a request
+    inputTimeOut = 60 * 4
+
+    # maximum length of headers (10KiB)
+    maxHeaderLength = 10240
+
+    # Allow persistent connections?
+    allowPersistentConnections = True
+    
+    # ChannelRequest
+    chanRequestFactory = HTTPChannelRequest
+    requestFactory = http.Request
+    
+    
+    _first_line = 2
+    readPersistent = PERSIST_PIPELINE
+    
+    _readLost = False
+    _writeLost = False
+    
+    _lingerTimer = None
+    chanRequest = None
+
+    def _callLater(self, secs, fun):
+        reactor.callLater(secs, fun)
+    
+    def __init__(self):
+        # the request queue
+        self.requests = []
+        
+    def connectionMade(self):
+        self.setTimeout(self.inputTimeOut)
+        self.factory.outstandingRequests+=1
+    
+    def lineReceived(self, line):
+        if self._first_line:
+            self.setTimeout(self.inputTimeOut)
+            # if this connection is not persistent, drop any data which
+            # the client (illegally) sent after the last request.
+            if not self.readPersistent:
+                self.dataReceived = self.lineReceived = lambda *args: None
+                return
+
+            # IE sends an extraneous empty line (\r\n) after a POST request;
+            # eat up such a line, but only ONCE
+            if not line and self._first_line == 1:
+                self._first_line = 2
+                return
+            
+            self._first_line = 0
+            
+            if not self.allowPersistentConnections:
+                # Don't allow a second request
+                self.readPersistent = False
+                
+            try:
+                self.chanRequest = self.chanRequestFactory(self, len(self.requests))
+                self.requests.append(self.chanRequest)
+                self.chanRequest.gotInitialLine(line)
+            except AbortedException:
+                pass
+        else:
+            try:
+                self.chanRequest.lineReceived(line)
+            except AbortedException:
+                pass
+
+    def lineLengthExceeded(self, line):
+        if self._first_line:
+            # Fabricate a request object to respond to the line length violation.
+            self.chanRequest = self.chanRequestFactory(self, 
+                                                       len(self.requests))
+            self.requests.append(self.chanRequest)
+            self.chanRequest.gotInitialLine("GET fake HTTP/1.0")
+        try:
+            self.chanRequest.lineLengthExceeded(line, self._first_line)
+        except AbortedException:
+            pass
+            
+    def rawDataReceived(self, data):
+        self.setTimeout(self.inputTimeOut)
+        try:
+            self.chanRequest.rawDataReceived(data)
+        except AbortedException:
+            pass
+
+    def requestReadFinished(self, request):
+        if(self.readPersistent is PERSIST_NO_PIPELINE or
+           len(self.requests) >= self.maxPipeline):
+            self.pauseProducing()
+        
+        # reset state variables
+        self._first_line = 1
+        self.chanRequest = None
+        self.setLineMode()
+        
+        # Disable the idle timeout, in case this request takes a long
+        # time to finish generating output.
+        if len(self.requests) > 0:
+            self.setTimeout(None)
+        
+    def _startNextRequest(self):
+        # notify next request, if present, it can start writing
+        del self.requests[0]
+
+        if self._writeLost:
+            self.transport.loseConnection()
+        elif self.requests:
+            self.requests[0].noLongerQueued()
+            
+            # resume reading if allowed to
+            if(not self._readLost and
+               self.readPersistent is not PERSIST_NO_PIPELINE and
+               len(self.requests) < self.maxPipeline):
+                self.resumeProducing()
+        elif self._readLost:
+            # No more incoming data, they already closed!
+            self.transport.loseConnection()
+        else:
+            # no requests in queue, resume reading
+            self.setTimeout(self.betweenRequestsTimeOut)
+            self.resumeProducing()
+
+    def setReadPersistent(self, persistent):
+        if self.readPersistent:
+            # only allow it to be set if it's not currently False
+            self.readPersistent = persistent
+
+    def dropQueuedRequests(self):
+        """Called when a response is written that forces a connection close."""
+        self.readPersistent = False
+        # Tell all requests but first to abort.
+        for request in self.requests[1:]:
+            request.connectionLost(None)
+        del self.requests[1:]
+    
+    def isLastRequest(self, request):
+        # Is this channel handling the last possible request
+        return not self.readPersistent and self.requests[-1] == request
+    
+    def requestWriteFinished(self, request):
+        """Called by first request in queue when it is done."""
+        if request != self.requests[0]: raise TypeError
+
+        # Don't del because we haven't finished cleanup, so,
+        # don't want queue len to be 0 yet.
+        self.requests[0] = None
+        
+        if self.readPersistent or len(self.requests) > 1:
+            # Do this in the next reactor loop so as to
+            # not cause huge call stacks with fast
+            # incoming requests.
+            self._callLater(0, self._startNextRequest)
+        else:
+            self.lingeringClose()
+
+    def timeoutConnection(self):
+        #log.msg("Timing out client: %s" % str(self.transport.getPeer()))
+        policies.TimeoutMixin.timeoutConnection(self)
+
+    def lingeringClose(self):
+        """
+        This is a bit complicated. This process is necessary to ensure proper
+        workingness when HTTP pipelining is in use.
+
+        Here is what it wants to do:
+
+            1.  Finish writing any buffered data, then close our write side.
+                While doing so, read and discard any incoming data.
+
+            2.  When that happens (writeConnectionLost called), wait up to 20
+                seconds for the remote end to close their write side (our read
+                side).
+
+            3.
+                - If they do (readConnectionLost called), close the socket,
+                  and cancel the timeout.
+
+                - If that doesn't happen, the timer fires, and makes the
+                  socket close anyways.
+        """
+        
+        # Close write half
+        self.transport.loseWriteConnection()
+        
+        # Throw out any incoming data
+        self.dataReceived = self.lineReceived = lambda *args: None
+        self.transport.resumeProducing()
+
+    def writeConnectionLost(self):
+        # Okay, all data has been written
+        # In 20 seconds, actually close the socket
+        self._lingerTimer = reactor.callLater(20, self._lingerClose)
+        self._writeLost = True
+        
+    def _lingerClose(self):
+        self._lingerTimer = None
+        self.transport.loseConnection()
+        
+    def readConnectionLost(self):
+        """Read connection lost"""
+        # If in the lingering-close state, lose the socket.
+        if self._lingerTimer:
+            self._lingerTimer.cancel()
+            self._lingerTimer = None
+            self.transport.loseConnection()
+            return
+        
+        # If between requests, drop connection
+        # when all current requests have written their data.
+        self._readLost = True
+        if not self.requests:
+            # No requests in progress, lose now.
+            self.transport.loseConnection()
+            
+        # If currently in the process of reading a request, this is
+        # probably a client abort, so lose the connection.
+        if self.chanRequest:
+            self.transport.loseConnection()
+        
+    def connectionLost(self, reason):
+        self.factory.outstandingRequests-=1
+
+        self._writeLost = True
+        self.readConnectionLost()
+        self.setTimeout(None)
+        
+        # Tell all requests to abort.
+        for request in self.requests:
+            if request is not None:
+                request.connectionLost(reason)
+
+class OverloadedServerProtocol(protocol.Protocol):
+    def connectionMade(self):
+        self.transport.write("HTTP/1.0 503 Service Unavailable\r\n"
+                             "Content-Type: text/html\r\n"
+                             "Connection: close\r\n\r\n"
+                             "<html><head><title>503 Service Unavailable</title></head>"
+                             "<body><h1>Service Unavailable</h1>"
+                             "The server is currently overloaded, "
+                             "please try again later.</body></html>")
+        self.transport.loseConnection()
+
+class HTTPFactory(protocol.ServerFactory):
+    """Factory for HTTP server."""
+
+    protocol = HTTPChannel
+    
+    protocolArgs = None
+
+    outstandingRequests = 0
+    
+    def __init__(self, requestFactory, maxRequests=600, **kwargs):
+        self.maxRequests=maxRequests
+        self.protocolArgs = kwargs
+        self.protocolArgs['requestFactory']=requestFactory
+        
+    def buildProtocol(self, addr):
+        if self.outstandingRequests >= self.maxRequests:
+            return OverloadedServerProtocol()
+        
+        p = protocol.ServerFactory.buildProtocol(self, addr)
+        
+        for arg,value in self.protocolArgs.iteritems():
+            setattr(p, arg, value)
+        return p
+
+
 class HTTP503LoggingFactory (HTTPFactory):
     """
     Factory for HTTP server which emits a 503 response when overloaded.
@@ -205,21 +1118,9 @@
         return p
 
 
-
-class SSLRedirectRequest(Request):
-    """ For redirecting HTTP to HTTPS port """
-
-    def process(self):
-        if config.SSLPort == 443:
-            location = (
-                "https://%s%s"
-                % (config.ServerHostName, self.uri)
-            )
-        else:
-            location = (
-                "https://%s:%d%s"
-                % (config.ServerHostName, config.SSLPort, self.uri)
-            )
-        self.writeResponse(RedirectResponse(location))
-
-
+__all__ = [
+    "HTTPFactory",
+    "HTTP503LoggingFactory",
+    "LimitingHTTPFactory",
+    "SSLRedirectRequest",
+]

Deleted: CalendarServer/trunk/twext/web2/client/__init__.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/client/__init__.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/client/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,27 +0,0 @@
-# -*- test-case-name: twext.web2.test.test_client -*-
-##
-# Copyright (c) 2004 Twisted Matrix Laboratories.
-# Copyright (c) 2010 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.
-#
-##
-"""
-Twisted.web2.client: Client Implementation
-"""

Copied: CalendarServer/trunk/twext/web2/client/__init__.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/client/__init__.py)
===================================================================
--- CalendarServer/trunk/twext/web2/client/__init__.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/client/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,27 @@
+# -*- test-case-name: twext.web2.test.test_client -*-
+##
+# Copyright (c) 2004 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.
+#
+##
+"""
+Twisted.web2.client: Client Implementation
+"""

Deleted: CalendarServer/trunk/twext/web2/client/http.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/client/http.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/client/http.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,385 +0,0 @@
-# -*- test-case-name: twext.web2.test.test_client -*-
-##
-# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
-# Copyright (c) 2010 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.
-#
-##
-
-"""
-Client-side HTTP implementation.
-"""
-
-from zope.interface import implements
-
-from twisted.internet.defer import Deferred
-from twisted.protocols.basic import LineReceiver
-from twisted.protocols.policies import TimeoutMixin
-
-from twext.web2.responsecode import BAD_REQUEST, HTTP_VERSION_NOT_SUPPORTED
-from twext.web2.http import parseVersion, Response
-from twext.web2.http_headers import Headers
-from twext.web2.stream import ProducerStream, StreamProducer, IByteStream
-from twext.web2.channel.http import HTTPParser, PERSIST_NO_PIPELINE, PERSIST_PIPELINE
-from twext.web2.client.interfaces import IHTTPClientManager
-
-
-
-class ProtocolError(Exception):
-    """
-    Exception raised when a HTTP error happened.
-    """
-
-
-
-class ClientRequest(object):
-    """
-    A class for describing an HTTP request to be sent to the server.
-    """
-
-    def __init__(self, method, uri, headers, stream):
-        """
-        @param method: The HTTP method to for this request, ex: 'GET', 'HEAD',
-            'POST', etc.
-        @type method: C{str}
-
-        @param uri: The URI of the resource to request, this may be absolute or
-            relative, however the interpretation of this URI is left up to the
-            remote server.
-        @type uri: C{str}
-
-        @param headers: Headers to be sent to the server.  It is important to
-            note that this object does not create any implicit headers.  So it
-            is up to the HTTP Client to add required headers such as 'Host'.
-        @type headers: C{dict}, L{twext.web2.http_headers.Headers}, or
-            C{None}
-
-        @param stream: Content body to send to the remote HTTP server.
-        @type stream: L{twext.web2.stream.IByteStream}
-        """
-
-        self.method = method
-        self.uri = uri
-        if isinstance(headers, Headers):
-            self.headers = headers
-        else:
-            self.headers = Headers(headers or {})
-
-        if stream is not None:
-            self.stream = IByteStream(stream)
-        else:
-            self.stream = None
-
-
-
-class HTTPClientChannelRequest(HTTPParser):
-    parseCloseAsEnd = True
-    outgoing_version = "HTTP/1.1"
-    chunkedOut = False
-    finished = False
-
-    closeAfter = False
-
-    def __init__(self, channel, request, closeAfter):
-        HTTPParser.__init__(self, channel)
-        self.request = request
-        self.closeAfter = closeAfter
-        self.transport = self.channel.transport
-        self.responseDefer = Deferred()
-
-    def submit(self):
-        l = []
-        request = self.request
-        if request.method == "HEAD":
-            # No incoming data will arrive.
-            self.length = 0
-
-        l.append('%s %s %s\r\n' % (request.method, request.uri,
-                                   self.outgoing_version))
-        if request.headers is not None:
-            for name, valuelist in request.headers.getAllRawHeaders():
-                for value in valuelist:
-                    l.append("%s: %s\r\n" % (name, value))
-
-        if request.stream is not None:
-            if request.stream.length is not None:
-                l.append("%s: %s\r\n" % ('Content-Length', request.stream.length))
-            else:
-                # Got a stream with no length. Send as chunked and hope, against
-                # the odds, that the server actually supports chunked uploads.
-                l.append("%s: %s\r\n" % ('Transfer-Encoding', 'chunked'))
-                self.chunkedOut = True
-
-        if self.closeAfter:
-            l.append("%s: %s\r\n" % ('Connection', 'close'))
-        else:
-            l.append("%s: %s\r\n" % ('Connection', 'Keep-Alive'))
-
-        l.append("\r\n")
-        self.transport.writeSequence(l)
-
-        d = StreamProducer(request.stream).beginProducing(self)
-        d.addCallback(self._finish).addErrback(self._error)
-
-    def registerProducer(self, producer, streaming):
-        """
-        Register a producer.
-        """
-        self.transport.registerProducer(producer, streaming)
-
-    def unregisterProducer(self):
-        self.transport.unregisterProducer()
-
-    def write(self, data):
-        if not data:
-            return
-        elif self.chunkedOut:
-            self.transport.writeSequence(("%X\r\n" % len(data), data, "\r\n"))
-        else:
-            self.transport.write(data)
-
-    def _finish(self, x):
-        """
-        We are finished writing data.
-        """
-        if self.chunkedOut:
-            # write last chunk and closing CRLF
-            self.transport.write("0\r\n\r\n")
-
-        self.finished = True
-        self.channel.requestWriteFinished(self)
-        del self.transport
-
-    def _error(self, err):
-        """
-        Abort parsing, and depending of the status of the request, either fire
-        the C{responseDefer} if no response has been sent yet, or close the
-        stream.
-        """
-        self.abortParse()
-        if hasattr(self, 'stream') and self.stream is not None:
-            self.stream.finish(err)
-        else:
-            self.responseDefer.errback(err)
-
-    def _abortWithError(self, errcode, text):
-        """
-        Abort parsing by forwarding a C{ProtocolError} to C{_error}.
-        """
-        self._error(ProtocolError(text))
-
-    def connectionLost(self, reason):
-        self._error(reason)
-
-    def gotInitialLine(self, initialLine):
-        parts = initialLine.split(' ', 2)
-
-        # Parse the initial request line
-        if len(parts) != 3:
-            self._abortWithError(BAD_REQUEST,
-                                 "Bad response line: %s" % (initialLine,))
-            return
-
-        strversion, self.code, message = parts
-
-        try:
-            protovers = parseVersion(strversion)
-            if protovers[0] != 'http':
-                raise ValueError()
-        except ValueError:
-            self._abortWithError(BAD_REQUEST,
-                                 "Unknown protocol: %s" % (strversion,))
-            return
-
-        self.version = protovers[1:3]
-
-        # Ensure HTTP 0 or HTTP 1.
-        if self.version[0] != 1:
-            self._abortWithError(HTTP_VERSION_NOT_SUPPORTED,
-                                 'Only HTTP 1.x is supported.')
-            return
-
-    ## FIXME: Actually creates Response, function is badly named!
-    def createRequest(self):
-        self.stream = ProducerStream(self.length)
-        self.response = Response(self.code, self.inHeaders, self.stream)
-        self.stream.registerProducer(self, True)
-
-        del self.inHeaders
-
-    ## FIXME: Actually processes Response, function is badly named!
-    def processRequest(self):
-        self.responseDefer.callback(self.response)
-
-    def handleContentChunk(self, data):
-        self.stream.write(data)
-
-    def handleContentComplete(self):
-        self.stream.finish()
-
-
-
-class EmptyHTTPClientManager(object):
-    """
-    A dummy HTTPClientManager.  It doesn't do any client management, and is
-    meant to be used only when creating an HTTPClientProtocol directly.
-    """
-
-    implements(IHTTPClientManager)
-
-    def clientBusy(self, proto):
-        pass
-
-    def clientIdle(self, proto):
-        pass
-
-    def clientPipelining(self, proto):
-        pass
-
-    def clientGone(self, proto):
-        pass
-
-
-
-class HTTPClientProtocol(LineReceiver, TimeoutMixin, object):
-    """
-    A HTTP 1.1 Client with request pipelining support.
-    """
-
-    chanRequest = None
-    maxHeaderLength = 10240
-    firstLine = 1
-    readPersistent = PERSIST_NO_PIPELINE
-
-    # inputTimeOut should be pending whenever a complete request has
-    # been written but the complete response has not yet been
-    # received, and be reset every time data is received.
-    inputTimeOut = 60 * 4
-
-    def __init__(self, manager=None):
-        """
-        @param manager: The object this client reports it state to.
-        @type manager: L{IHTTPClientManager}
-        """
-
-        self.outRequest = None
-        self.inRequests = []
-        if manager is None:
-            manager = EmptyHTTPClientManager()
-        self.manager = manager
-
-    def lineReceived(self, line):
-        if not self.inRequests:
-            # server sending random unrequested data.
-            self.transport.loseConnection()
-            return
-
-        # If not currently writing this request, set timeout
-        if self.inRequests[0] is not self.outRequest:
-            self.setTimeout(self.inputTimeOut)
-
-        if self.firstLine:
-            self.firstLine = 0
-            self.inRequests[0].gotInitialLine(line)
-        else:
-            self.inRequests[0].lineReceived(line)
-
-    def rawDataReceived(self, data):
-        if not self.inRequests:
-            # Server sending random unrequested data.
-            self.transport.loseConnection()
-            return
-
-        # If not currently writing this request, set timeout
-        if self.inRequests[0] is not self.outRequest:
-            self.setTimeout(self.inputTimeOut)
-
-        self.inRequests[0].rawDataReceived(data)
-
-    def submitRequest(self, request, closeAfter=True):
-        """
-        @param request: The request to send to a remote server.
-        @type request: L{ClientRequest}
-
-        @param closeAfter: If True the 'Connection: close' header will be sent,
-            otherwise 'Connection: keep-alive'
-        @type closeAfter: C{bool}
-
-        @rtype: L{twisted.internet.defer.Deferred}
-        @return: A Deferred which will be called back with the
-            L{twext.web2.http.Response} from the server.
-        """
-
-        # Assert we're in a valid state to submit more
-        assert self.outRequest is None
-        assert ((self.readPersistent is PERSIST_NO_PIPELINE
-                 and not self.inRequests)
-                or self.readPersistent is PERSIST_PIPELINE)
-
-        self.manager.clientBusy(self)
-        if closeAfter:
-            self.readPersistent = False
-
-        self.outRequest = chanRequest = HTTPClientChannelRequest(self,
-                                            request, closeAfter)
-        self.inRequests.append(chanRequest)
-
-        chanRequest.submit()
-        return chanRequest.responseDefer
-
-    def requestWriteFinished(self, request):
-        assert request is self.outRequest
-
-        self.outRequest = None
-        # Tell the manager if more requests can be submitted.
-        self.setTimeout(self.inputTimeOut)
-        if self.readPersistent is PERSIST_PIPELINE:
-            self.manager.clientPipelining(self)
-
-    def requestReadFinished(self, request):
-        assert self.inRequests[0] is request
-
-        del self.inRequests[0]
-        self.firstLine = True
-
-        if not self.inRequests:
-            if self.readPersistent:
-                self.setTimeout(None)
-                self.manager.clientIdle(self)
-            else:
-                self.transport.loseConnection()
-
-    def setReadPersistent(self, persist):
-        self.readPersistent = persist
-        if not persist:
-            # Tell all requests but first to abort.
-            for request in self.inRequests[1:]:
-                request.connectionLost(None)
-            del self.inRequests[1:]
-
-    def connectionLost(self, reason):
-        self.readPersistent = False
-        self.setTimeout(None)
-        self.manager.clientGone(self)
-        # Tell all requests to abort.
-        for request in self.inRequests:
-            if request is not None:
-                request.connectionLost(reason)
-

Copied: CalendarServer/trunk/twext/web2/client/http.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/client/http.py)
===================================================================
--- CalendarServer/trunk/twext/web2/client/http.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/client/http.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,385 @@
+# -*- test-case-name: twext.web2.test.test_client -*-
+##
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.
+#
+##
+
+"""
+Client-side HTTP implementation.
+"""
+
+from zope.interface import implements
+
+from twisted.internet.defer import Deferred
+from twisted.protocols.basic import LineReceiver
+from twisted.protocols.policies import TimeoutMixin
+
+from twext.web2.responsecode import BAD_REQUEST, HTTP_VERSION_NOT_SUPPORTED
+from twext.web2.http import parseVersion, Response
+from twext.web2.http_headers import Headers
+from twext.web2.stream import ProducerStream, StreamProducer, IByteStream
+from twext.web2.channel.http import HTTPParser, PERSIST_NO_PIPELINE, PERSIST_PIPELINE
+from twext.web2.client.interfaces import IHTTPClientManager
+
+
+
+class ProtocolError(Exception):
+    """
+    Exception raised when a HTTP error happened.
+    """
+
+
+
+class ClientRequest(object):
+    """
+    A class for describing an HTTP request to be sent to the server.
+    """
+
+    def __init__(self, method, uri, headers, stream):
+        """
+        @param method: The HTTP method to for this request, ex: 'GET', 'HEAD',
+            'POST', etc.
+        @type method: C{str}
+
+        @param uri: The URI of the resource to request, this may be absolute or
+            relative, however the interpretation of this URI is left up to the
+            remote server.
+        @type uri: C{str}
+
+        @param headers: Headers to be sent to the server.  It is important to
+            note that this object does not create any implicit headers.  So it
+            is up to the HTTP Client to add required headers such as 'Host'.
+        @type headers: C{dict}, L{twext.web2.http_headers.Headers}, or
+            C{None}
+
+        @param stream: Content body to send to the remote HTTP server.
+        @type stream: L{twext.web2.stream.IByteStream}
+        """
+
+        self.method = method
+        self.uri = uri
+        if isinstance(headers, Headers):
+            self.headers = headers
+        else:
+            self.headers = Headers(headers or {})
+
+        if stream is not None:
+            self.stream = IByteStream(stream)
+        else:
+            self.stream = None
+
+
+
+class HTTPClientChannelRequest(HTTPParser):
+    parseCloseAsEnd = True
+    outgoing_version = "HTTP/1.1"
+    chunkedOut = False
+    finished = False
+
+    closeAfter = False
+
+    def __init__(self, channel, request, closeAfter):
+        HTTPParser.__init__(self, channel)
+        self.request = request
+        self.closeAfter = closeAfter
+        self.transport = self.channel.transport
+        self.responseDefer = Deferred()
+
+    def submit(self):
+        l = []
+        request = self.request
+        if request.method == "HEAD":
+            # No incoming data will arrive.
+            self.length = 0
+
+        l.append('%s %s %s\r\n' % (request.method, request.uri,
+                                   self.outgoing_version))
+        if request.headers is not None:
+            for name, valuelist in request.headers.getAllRawHeaders():
+                for value in valuelist:
+                    l.append("%s: %s\r\n" % (name, value))
+
+        if request.stream is not None:
+            if request.stream.length is not None:
+                l.append("%s: %s\r\n" % ('Content-Length', request.stream.length))
+            else:
+                # Got a stream with no length. Send as chunked and hope, against
+                # the odds, that the server actually supports chunked uploads.
+                l.append("%s: %s\r\n" % ('Transfer-Encoding', 'chunked'))
+                self.chunkedOut = True
+
+        if self.closeAfter:
+            l.append("%s: %s\r\n" % ('Connection', 'close'))
+        else:
+            l.append("%s: %s\r\n" % ('Connection', 'Keep-Alive'))
+
+        l.append("\r\n")
+        self.transport.writeSequence(l)
+
+        d = StreamProducer(request.stream).beginProducing(self)
+        d.addCallback(self._finish).addErrback(self._error)
+
+    def registerProducer(self, producer, streaming):
+        """
+        Register a producer.
+        """
+        self.transport.registerProducer(producer, streaming)
+
+    def unregisterProducer(self):
+        self.transport.unregisterProducer()
+
+    def write(self, data):
+        if not data:
+            return
+        elif self.chunkedOut:
+            self.transport.writeSequence(("%X\r\n" % len(data), data, "\r\n"))
+        else:
+            self.transport.write(data)
+
+    def _finish(self, x):
+        """
+        We are finished writing data.
+        """
+        if self.chunkedOut:
+            # write last chunk and closing CRLF
+            self.transport.write("0\r\n\r\n")
+
+        self.finished = True
+        self.channel.requestWriteFinished(self)
+        del self.transport
+
+    def _error(self, err):
+        """
+        Abort parsing, and depending of the status of the request, either fire
+        the C{responseDefer} if no response has been sent yet, or close the
+        stream.
+        """
+        self.abortParse()
+        if hasattr(self, 'stream') and self.stream is not None:
+            self.stream.finish(err)
+        else:
+            self.responseDefer.errback(err)
+
+    def _abortWithError(self, errcode, text):
+        """
+        Abort parsing by forwarding a C{ProtocolError} to C{_error}.
+        """
+        self._error(ProtocolError(text))
+
+    def connectionLost(self, reason):
+        self._error(reason)
+
+    def gotInitialLine(self, initialLine):
+        parts = initialLine.split(' ', 2)
+
+        # Parse the initial request line
+        if len(parts) != 3:
+            self._abortWithError(BAD_REQUEST,
+                                 "Bad response line: %s" % (initialLine,))
+            return
+
+        strversion, self.code, message = parts
+
+        try:
+            protovers = parseVersion(strversion)
+            if protovers[0] != 'http':
+                raise ValueError()
+        except ValueError:
+            self._abortWithError(BAD_REQUEST,
+                                 "Unknown protocol: %s" % (strversion,))
+            return
+
+        self.version = protovers[1:3]
+
+        # Ensure HTTP 0 or HTTP 1.
+        if self.version[0] != 1:
+            self._abortWithError(HTTP_VERSION_NOT_SUPPORTED,
+                                 'Only HTTP 1.x is supported.')
+            return
+
+    ## FIXME: Actually creates Response, function is badly named!
+    def createRequest(self):
+        self.stream = ProducerStream(self.length)
+        self.response = Response(self.code, self.inHeaders, self.stream)
+        self.stream.registerProducer(self, True)
+
+        del self.inHeaders
+
+    ## FIXME: Actually processes Response, function is badly named!
+    def processRequest(self):
+        self.responseDefer.callback(self.response)
+
+    def handleContentChunk(self, data):
+        self.stream.write(data)
+
+    def handleContentComplete(self):
+        self.stream.finish()
+
+
+
+class EmptyHTTPClientManager(object):
+    """
+    A dummy HTTPClientManager.  It doesn't do any client management, and is
+    meant to be used only when creating an HTTPClientProtocol directly.
+    """
+
+    implements(IHTTPClientManager)
+
+    def clientBusy(self, proto):
+        pass
+
+    def clientIdle(self, proto):
+        pass
+
+    def clientPipelining(self, proto):
+        pass
+
+    def clientGone(self, proto):
+        pass
+
+
+
+class HTTPClientProtocol(LineReceiver, TimeoutMixin, object):
+    """
+    A HTTP 1.1 Client with request pipelining support.
+    """
+
+    chanRequest = None
+    maxHeaderLength = 10240
+    firstLine = 1
+    readPersistent = PERSIST_NO_PIPELINE
+
+    # inputTimeOut should be pending whenever a complete request has
+    # been written but the complete response has not yet been
+    # received, and be reset every time data is received.
+    inputTimeOut = 60 * 4
+
+    def __init__(self, manager=None):
+        """
+        @param manager: The object this client reports it state to.
+        @type manager: L{IHTTPClientManager}
+        """
+
+        self.outRequest = None
+        self.inRequests = []
+        if manager is None:
+            manager = EmptyHTTPClientManager()
+        self.manager = manager
+
+    def lineReceived(self, line):
+        if not self.inRequests:
+            # server sending random unrequested data.
+            self.transport.loseConnection()
+            return
+
+        # If not currently writing this request, set timeout
+        if self.inRequests[0] is not self.outRequest:
+            self.setTimeout(self.inputTimeOut)
+
+        if self.firstLine:
+            self.firstLine = 0
+            self.inRequests[0].gotInitialLine(line)
+        else:
+            self.inRequests[0].lineReceived(line)
+
+    def rawDataReceived(self, data):
+        if not self.inRequests:
+            # Server sending random unrequested data.
+            self.transport.loseConnection()
+            return
+
+        # If not currently writing this request, set timeout
+        if self.inRequests[0] is not self.outRequest:
+            self.setTimeout(self.inputTimeOut)
+
+        self.inRequests[0].rawDataReceived(data)
+
+    def submitRequest(self, request, closeAfter=True):
+        """
+        @param request: The request to send to a remote server.
+        @type request: L{ClientRequest}
+
+        @param closeAfter: If True the 'Connection: close' header will be sent,
+            otherwise 'Connection: keep-alive'
+        @type closeAfter: C{bool}
+
+        @rtype: L{twisted.internet.defer.Deferred}
+        @return: A Deferred which will be called back with the
+            L{twext.web2.http.Response} from the server.
+        """
+
+        # Assert we're in a valid state to submit more
+        assert self.outRequest is None
+        assert ((self.readPersistent is PERSIST_NO_PIPELINE
+                 and not self.inRequests)
+                or self.readPersistent is PERSIST_PIPELINE)
+
+        self.manager.clientBusy(self)
+        if closeAfter:
+            self.readPersistent = False
+
+        self.outRequest = chanRequest = HTTPClientChannelRequest(self,
+                                            request, closeAfter)
+        self.inRequests.append(chanRequest)
+
+        chanRequest.submit()
+        return chanRequest.responseDefer
+
+    def requestWriteFinished(self, request):
+        assert request is self.outRequest
+
+        self.outRequest = None
+        # Tell the manager if more requests can be submitted.
+        self.setTimeout(self.inputTimeOut)
+        if self.readPersistent is PERSIST_PIPELINE:
+            self.manager.clientPipelining(self)
+
+    def requestReadFinished(self, request):
+        assert self.inRequests[0] is request
+
+        del self.inRequests[0]
+        self.firstLine = True
+
+        if not self.inRequests:
+            if self.readPersistent:
+                self.setTimeout(None)
+                self.manager.clientIdle(self)
+            else:
+                self.transport.loseConnection()
+
+    def setReadPersistent(self, persist):
+        self.readPersistent = persist
+        if not persist:
+            # Tell all requests but first to abort.
+            for request in self.inRequests[1:]:
+                request.connectionLost(None)
+            del self.inRequests[1:]
+
+    def connectionLost(self, reason):
+        self.readPersistent = False
+        self.setTimeout(None)
+        self.manager.clientGone(self)
+        # Tell all requests to abort.
+        for request in self.inRequests:
+            if request is not None:
+                request.connectionLost(reason)
+

Deleted: CalendarServer/trunk/twext/web2/client/interfaces.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/client/interfaces.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/client/interfaces.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,65 +0,0 @@
-# -*- test-case-name: twext.web2.test.test_client -*-
-##
-# Copyright (c) 2007 Twisted Matrix Laboratories.
-# Copyright (c) 2010 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 zope.interface import Interface
-
-class IHTTPClientManager(Interface):
-    """I coordinate between multiple L{HTTPClientProtocol} objects connected to a 
-    single server to facilite request queuing and pipelining.
-    """
-
-    def clientBusy(proto):
-        """Called when the L{HTTPClientProtocol} doesn't want to accept anymore
-        requests.
-
-        @param proto: The L{HTTPClientProtocol} that is changing state.
-        @type proto: L{HTTPClientProtocol}        
-        """
-        pass
-    
-    def clientIdle(proto):
-        """Called when an L{HTTPClientProtocol} is able to accept more requests.
-    
-        @param proto: The L{HTTPClientProtocol} that is changing state.
-        @type proto: L{HTTPClientProtocol}
-        """
-        pass
-
-    def clientPipelining(proto):
-        """Called when the L{HTTPClientProtocol} determines that it is able to
-        support request pipelining.
-    
-        @param proto: The L{HTTPClientProtocol} that is changing state.
-        @type proto: L{HTTPClientProtocol}
-        """
-        pass
-    
-    def clientGone(proto):
-        """Called when the L{HTTPClientProtocol} disconnects from the server.
-
-        @param proto: The L{HTTPClientProtocol} that is changing state.
-        @type proto: L{HTTPClientProtocol}
-        """
-        pass

Copied: CalendarServer/trunk/twext/web2/client/interfaces.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/client/interfaces.py)
===================================================================
--- CalendarServer/trunk/twext/web2/client/interfaces.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/client/interfaces.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,65 @@
+# -*- test-case-name: twext.web2.test.test_client -*-
+##
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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 zope.interface import Interface
+
+class IHTTPClientManager(Interface):
+    """I coordinate between multiple L{HTTPClientProtocol} objects connected to a 
+    single server to facilite request queuing and pipelining.
+    """
+
+    def clientBusy(proto):
+        """Called when the L{HTTPClientProtocol} doesn't want to accept anymore
+        requests.
+
+        @param proto: The L{HTTPClientProtocol} that is changing state.
+        @type proto: L{HTTPClientProtocol}        
+        """
+        pass
+    
+    def clientIdle(proto):
+        """Called when an L{HTTPClientProtocol} is able to accept more requests.
+    
+        @param proto: The L{HTTPClientProtocol} that is changing state.
+        @type proto: L{HTTPClientProtocol}
+        """
+        pass
+
+    def clientPipelining(proto):
+        """Called when the L{HTTPClientProtocol} determines that it is able to
+        support request pipelining.
+    
+        @param proto: The L{HTTPClientProtocol} that is changing state.
+        @type proto: L{HTTPClientProtocol}
+        """
+        pass
+    
+    def clientGone(proto):
+        """Called when the L{HTTPClientProtocol} disconnects from the server.
+
+        @param proto: The L{HTTPClientProtocol} that is changing state.
+        @type proto: L{HTTPClientProtocol}
+        """
+        pass

Copied: CalendarServer/trunk/twext/web2/compat.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/compat.py)
===================================================================
--- CalendarServer/trunk/twext/web2/compat.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/compat.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,473 @@
+
+##
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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 __future__ import generators
+
+from urllib import quote, string
+
+import UserDict, math, time
+from cStringIO import StringIO
+
+from twext.web2 import http_headers, iweb, stream, responsecode
+from twisted.internet import defer, address
+from twisted.python import components
+from twisted.spread import pb
+
+from zope.interface import implements
+
+class HeaderAdapter(UserDict.DictMixin):
+    def __init__(self, headers):
+        self._headers = headers
+        
+    def __getitem__(self, name):
+        raw = self._headers.getRawHeaders(name)
+        if raw is None:
+            raise KeyError(name)
+        return ', '.join(raw)
+
+    def __setitem__(self, name, value):
+        self._headers.setRawHeaders([value])
+        
+    def __delitem__(self, name):
+        if not self._headers.hasHeader(name):
+            raise KeyError(name)
+        self._headers.removeHeader(name)
+
+    def iteritems(self):
+        for k,v in self._headers.getAllRawHeaders():
+            yield k, ', '.join(v)
+
+    def keys(self):
+        return [k for k, _ in self.iteritems()]
+
+    def __iter__(self):
+        for k, _ in self.iteritems():
+            yield k
+
+    def has_key(self, name):
+        return self._headers.hasHeader(name)
+
+def makeOldRequestAdapter(original):
+    # Cache the adapter. Replace this with a more better generalized
+    # mechanism when one becomes available.
+    if not hasattr(original, '_oldRequest'):
+        original._oldRequest = OldRequestAdapter(original)
+    return original._oldRequest
+
+def _addressToTuple(addr):
+    if isinstance(addr, address.IPv4Address):
+        return ('INET', addr.host, addr.port)
+    elif isinstance(addr, address.UNIXAddress):
+        return ('UNIX', addr.name)
+    else:
+        return tuple(addr)
+
+class OldRequestAdapter(pb.Copyable, components.Componentized, object):
+    """Adapt old requests to new request
+    """
+    implements(iweb.IOldRequest)
+    
+    def _getFrom(where, name):
+        def _get(self):
+            return getattr(getattr(self, where), name)
+        return property(_get)
+
+    def _getsetFrom(where, name):
+        def _get(self):
+            return getattr(getattr(self, where), name)
+        def _set(self, new):
+            setattr(getattr(self, where), name, new)
+        def _del(self):
+            delattr(getattr(self, where), name)
+        return property(_get, _set, _del)
+
+    def _getsetHeaders(where):
+        def _get(self):
+            headers = getattr(self, where).headers
+            return HeaderAdapter(headers)
+
+        def _set(self, newheaders):
+            headers = http_headers.Headers()
+            for n,v in newheaders.items():
+                headers.setRawHeaders(n, (v,))
+            newheaders = headers
+            getattr(self, where).headers = newheaders
+            
+        return property(_get, _set)
+    
+    
+    code = _getsetFrom('response', 'code')
+    code_message = ""
+    
+    method = _getsetFrom('request', 'method')
+    uri = _getsetFrom('request', 'uri')
+    def _getClientproto(self):
+        return "HTTP/%d.%d" % self.request.clientproto
+    clientproto = property(_getClientproto)
+    
+    received_headers = _getsetHeaders('request')
+    headers = _getsetHeaders('response')
+    path = _getsetFrom('request', 'path')
+    
+    # cookies = # Do I need this?
+    # received_cookies = # Do I need this?
+    content = StringIO() #### FIXME
+    args = _getsetFrom('request', 'args')
+    # stack = # WTF is stack?
+    prepath = _getsetFrom('request', 'prepath')
+    postpath = _getsetFrom('request', 'postpath')
+
+    def _getClient(self):
+        return "WTF"
+    client = property(_getClient)
+    
+    def _getHost(self):
+        return address.IPv4Address("TCP", self.request.host, self.request.port)
+    host = property(_getHost)
+    
+    def __init__(self, request):
+        from twext.web2 import http
+        components.Componentized.__init__(self)
+        self.request = request
+        self.response = http.Response(stream=stream.ProducerStream())
+        # This deferred will be fired by the first call to write on OldRequestAdapter
+        # and will cause the headers to be output.
+        self.deferredResponse = defer.Deferred()
+
+    def getStateToCopyFor(self, issuer):
+        # This is for distrib compatibility
+        x = {}
+
+        x['prepath'] = self.prepath
+        x['postpath'] = self.postpath
+        x['method'] = self.method
+        x['uri'] = self.uri
+
+        x['clientproto'] = self.clientproto
+        self.content.seek(0, 0)
+        x['content_data'] = self.content.read()
+        x['remote'] = pb.ViewPoint(issuer, self)
+
+        x['host'] = _addressToTuple(self.request.chanRequest.channel.transport.getHost())
+        x['client'] = _addressToTuple(self.request.chanRequest.channel.transport.getPeer())
+
+        return x
+
+    def getTypeToCopy(self):
+        # lie to PB so the ResourcePublisher doesn't have to know web2 exists
+        # which is good because web2 doesn't exist.
+        return 'twisted.web.server.Request'
+
+    def registerProducer(self, producer, streaming):
+        self.response.stream.registerProducer(producer, streaming)
+        
+    def unregisterProducer(self):
+        self.response.stream.unregisterProducer()
+        
+    def finish(self):
+        if self.deferredResponse is not None:
+            d = self.deferredResponse
+            self.deferredResponse = None
+            d.callback(self.response)
+        self.response.stream.finish()
+        
+    def write(self, data):
+        if self.deferredResponse is not None:
+            d = self.deferredResponse
+            self.deferredResponse = None
+            d.callback(self.response)
+        self.response.stream.write(data)
+        
+    def getHeader(self, name):
+        raw = self.request.headers.getRawHeaders(name)
+        if raw is None:
+            return None
+        return ', '.join(raw)
+
+    def setHeader(self, name, value):
+        """Set an outgoing HTTP header.
+        """
+        self.response.headers.setRawHeaders(name, [value])
+        
+    def setResponseCode(self, code, message=None):
+        # message ignored
+        self.response.code = code
+
+    def setLastModified(self, when):
+        # Never returns CACHED -- can it and still be compliant?
+        when = long(math.ceil(when))
+        self.response.headers.setHeader('last-modified', when)
+        return None
+
+    def setETag(self, etag):
+        self.response.headers.setRawHeaders('etag', [etag])
+        return None
+
+    def getAllHeaders(self):
+        return dict(self.headers.iteritems())
+
+    def getRequestHostname(self):
+        return self.request.host
+
+
+    def getCookie(self, key):
+        for cookie in self.request.headers.getHeader('cookie', ()):
+            if cookie.name == key:
+                return cookie.value
+            
+        return None
+
+    def addCookie(self, k, v, expires=None, domain=None, path=None, max_age=None, comment=None, secure=None):
+        if expires is None and max_age is not None:
+            expires=max_age-time.time()
+        cookie = http_headers.Cookie(k,v, expires=expires, domain=domain, path=path, comment=comment, secure=secure)
+        self.response.headers.setHeader('set-cookie', self.request.headers.getHeader('set-cookie', ())+(cookie,))
+
+    def notifyFinish(self):
+        ### FIXME
+        return None
+#        return self.request.notifyFinish()
+    
+    def getHost(self):
+        return self.host
+    
+    def setHost(self, host, port, ssl=0):
+        self.request.host = host
+        self.request.port = port
+        self.request.scheme = ssl and 'https' or 'http'
+
+    def isSecure(self):
+        return self.request.scheme == 'https'
+    
+    def getClientIP(self):
+        if isinstance(self.request.chanRequest.getRemoteHost(), address.IPv4Address):
+            return self.client.host
+        else:
+            return None
+        return self.request.chanRequest.getRemoteHost()
+        return "127.0.0.1"
+
+    def getClient(self):
+        return "127.0.0.1"
+
+### FIXME:
+    def getUser(self):
+        return ""
+
+    def getPassword(self):
+        return ""
+
+# Identical to original methods -- hopefully these don't have to change
+    def sibLink(self, name):
+        "Return the text that links to a sibling of the requested resource."
+        if self.postpath:
+            return (len(self.postpath)*"../") + name
+        else:
+            return name
+
+    def childLink(self, name):
+        "Return the text that links to a child of the requested resource."
+        lpp = len(self.postpath)
+        if lpp > 1:
+            return ((lpp-1)*"../") + name
+        elif lpp == 1:
+            return name
+        else: # lpp == 0
+            if len(self.prepath) and self.prepath[-1]:
+                return self.prepath[-1] + '/' + name
+            else:
+                return name
+
+    def redirect(self, url):
+        """Utility function that does a redirect.
+        
+        The request should have finish() called after this.
+        """
+        self.setResponseCode(responsecode.FOUND)
+        self.setHeader("location", url)
+    
+    def prePathURL(self):
+        port = self.getHost().port
+        if self.isSecure():
+            default = 443
+        else:
+            default = 80
+        if port == default:
+            hostport = ''
+        else:
+            hostport = ':%d' % port
+        return quote('http%s://%s%s/%s' % (
+            self.isSecure() and 's' or '',
+            self.getRequestHostname(),
+            hostport,
+            string.join(self.prepath, '/')), "/:")
+
+#     def URLPath(self):
+#         from twisted.python import urlpath
+#         return urlpath.URLPath.fromRequest(self)
+
+# But nevow wants it to look like this... :(
+    def URLPath(self):
+        from nevow import url
+        return url.URL.fromContext(self)
+
+    def rememberRootURL(self, url=None):
+        """
+        Remember the currently-processed part of the URL for later
+        recalling.
+        """
+        if url is None:
+            url = self.prePathURL()
+            # remove one segment
+            self.appRootURL = url[:url.rindex("/")]
+        else:
+            self.appRootURL = url
+
+    def getRootURL(self):
+        """
+        Get a previously-remembered URL.
+        """
+        return self.appRootURL
+
+    
+    session = None
+
+    def getSession(self, sessionInterface = None):
+        # Session management
+        if not self.session:
+            # FIXME: make sitepath be something
+            cookiename = string.join(['TWISTED_SESSION'] + self.sitepath, "_")
+            sessionCookie = self.getCookie(cookiename)
+            if sessionCookie:
+                try:
+                    self.session = self.site.getSession(sessionCookie)
+                except KeyError:
+                    pass
+            # if it still hasn't been set, fix it up.
+            if not self.session:
+                self.session = self.site.makeSession()
+                self.addCookie(cookiename, self.session.uid, path='/')
+        self.session.touch()
+        if sessionInterface:
+            return self.session.getComponent(sessionInterface)
+        return self.session
+
+
+class OldNevowResourceAdapter(object):
+    implements(iweb.IResource)
+    
+    def __init__(self, original):
+        # Can't use self.__original= because of __setattr__.
+        self.__dict__['_OldNevowResourceAdapter__original']=original
+        
+    def __getattr__(self, name):
+        return getattr(self.__original, name)
+
+    def __setattr__(self, name, value):
+        setattr(self.__original, name, value)
+
+    def __delattr__(self, name):
+        delattr(self.__original, name)
+
+    def locateChild(self, ctx, segments):
+        from twext.web2.server import parsePOSTData
+        request = iweb.IRequest(ctx)
+        if request.method == "POST":
+            return parsePOSTData(request).addCallback(
+                lambda x: self.__original.locateChild(ctx, segments))
+        return self.__original.locateChild(ctx, segments)
+    
+    def renderHTTP(self, ctx):
+        from twext.web2.server import parsePOSTData
+        request = iweb.IRequest(ctx)
+        if request.method == "POST":
+            return parsePOSTData(request).addCallback(self.__reallyRender, ctx)
+        return self.__reallyRender(None, ctx)
+
+    def __reallyRender(self, ignored, ctx):
+        # This deferred will be called when our resource is _finished_
+        # writing, and will make sure we write the rest of our data
+        # and finish the connection.
+        defer.maybeDeferred(self.__original.renderHTTP, ctx).addCallback(self.__finish, ctx)
+
+        # Sometimes the __original.renderHTTP will write() before we
+        # even get this far, and we don't want to return
+        # oldRequest.deferred if it's already been set to None.
+        oldRequest = iweb.IOldRequest(ctx)
+        if oldRequest.deferredResponse is None:
+            return oldRequest.response
+        return oldRequest.deferredResponse
+
+    def __finish(self, data, ctx):
+        oldRequest = iweb.IOldRequest(ctx)
+        oldRequest.write(data)
+        oldRequest.finish()
+
+
+class OldResourceAdapter(object):
+    implements(iweb.IOldNevowResource)
+
+    def __init__(self, original):
+        self.original = original
+
+    def __repr__(self):
+        return "<%s @ 0x%x adapting %r>" % (self.__class__.__name__, id(self), self.original)
+
+    def locateChild(self, req, segments):
+        import server
+        request = iweb.IOldRequest(req)
+        if self.original.isLeaf:
+            return self, server.StopTraversal
+        name = segments[0]
+        if name == '':
+            res = self
+        else:
+            request.prepath.append(request.postpath.pop(0))
+            res = self.original.getChildWithDefault(name, request)
+            request.postpath.insert(0, request.prepath.pop())
+            
+            if isinstance(res, defer.Deferred):
+                return res.addCallback(lambda res: (res, segments[1:]))
+            
+        return res, segments[1:]
+
+    def _handle_NOT_DONE_YET(self, data, request):
+        from twisted.web.server import NOT_DONE_YET
+        if data == NOT_DONE_YET:
+            # Return a deferred that will never fire, so the finish
+            # callback doesn't happen. This is because, when returning
+            # NOT_DONE_YET, the page is responsible for calling finish.
+            return defer.Deferred()
+        else:
+            return data
+
+    def renderHTTP(self, req):
+        request = iweb.IOldRequest(req)
+        result = defer.maybeDeferred(self.original.render, request).addCallback(
+            self._handle_NOT_DONE_YET, request)
+        return result
+
+__all__ = []

Modified: CalendarServer/trunk/twext/web2/dav/__init__.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/__init__.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,28 +1,56 @@
+# -*- test-case-name: twext.web2.dav.test -*-
+
 ##
-# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# Copyright (c) 2005-2010 Apple Computer, 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
+# 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:
 #
-# http://www.apache.org/licenses/LICENSE-2.0
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
 #
-# 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.
+# 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.
+#
 ##
 
 """
-Extensions to twisted.web2.dav
+WebDAV support for Twext.Web2.
+
+See RFC 2616: http://www.ietf.org/rfc/rfc2616.txt (HTTP)
+See RFC 2518: http://www.ietf.org/rfc/rfc2518.txt (WebDAV)
+See RFC 3253: http://www.ietf.org/rfc/rfc3253.txt (WebDAV Versioning Extentions)
+See RFC 3744: http://www.ietf.org/rfc/rfc3744.txt (WebDAV Access Control Protocol)
+
+See also: http://skrb.org/ietf/http_errata.html (Errata to RFC 2616)
 """
 
-#
-# Register additional WebDAV XML elements
-#
+__version__ = 'SVN-Trunk'
+version = __version__
 
+__all__ = [
+    "auth",
+    "fileop",
+    "davxml",
+    "http",
+    "idav",
+    "noneprops",
+    "resource",
+    "static",
+    "stream",
+    "util",
+    "xattrprops",
+]
+
 from twext.web2.dav import davxml
-import twisted.web2.dav.davxml
 
-twisted.web2.dav.davxml.registerElements(davxml)

Copied: CalendarServer/trunk/twext/web2/dav/_errorbase.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/_errorbase.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/_errorbase.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/_errorbase.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,67 @@
+##
+# Copyright (c) 2010 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.
+#
+##
+
+"""
+This module provides a point to avoid a circular dependency between the davxml
+and http modules.
+"""
+
+from twext.web2.http_headers import MimeType
+from twext.web2.http import Response
+
+class ErrorResponse (Response):
+    """
+    A L{Response} object which contains a status code and a L{davxml.Error}
+    element.
+    Renders itself as a DAV:error XML document.
+    """
+    error = None
+
+    def __init__(self, code, error, stream=None):
+        """
+        @param code: a response code.
+        @param error: an L{davxml.WebDAVElement} identifying the error, or a
+            tuple C{(namespace, name)} with which to create an empty element
+            denoting the error.  (The latter is useful in the case of
+            preconditions ans postconditions, not all of which have defined
+            XML element classes.)
+        """
+        from twext.web2.dav import davxml
+
+        if type(error) is tuple:
+            xml_namespace, xml_name = error
+            error = davxml.WebDAVUnknownElement()
+            error.namespace = xml_namespace
+            error.name = xml_name
+
+        if stream is None:
+            stream = davxml.Error(error).toxml()
+
+        Response.__init__(self, code=code, stream=stream)
+
+        self.headers.setHeader("content-type", MimeType("text", "xml"))
+
+        self.error = error
+
+    def __repr__(self):
+        return "<%s %s %s>" % (self.__class__.__name__, self.code, self.error.sname())

Copied: CalendarServer/trunk/twext/web2/dav/auth.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/auth.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/auth.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/auth.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,110 @@
+from zope.interface import implements, Interface
+from twisted.internet import defer
+from twisted.cred import checkers, error, portal
+from twext.web2.resource import WrapperResource
+from twext.web2.dav import davxml
+from twext.web2.dav.davxml import twisted_private_namespace
+
+__all__ = [
+    "IPrincipal",
+    "DavRealm",
+    "IPrincipalCredentials",
+    "PrincipalCredentials",
+    "AuthenticationWrapper",
+]
+
+class AuthenticationWrapper(WrapperResource):
+    def __init__(self, resource, portal, credentialFactories, loginInterfaces):
+        """
+        Wrap the given resource and use the parameters to set up the request
+        to allow anyone to challenge and handle authentication.
+
+        @param resource: L{DAVResource} FIXME: This should get promoted to
+            twext.web2.auth
+        @param portal: The cred portal
+        @param credentialFactories: Sequence of credentialFactories that can
+            be used to authenticate by resources in this tree.
+        @param loginInterfaces: More cred stuff
+        """
+        super(AuthenticationWrapper, self).__init__(resource)
+
+        self.portal = portal
+        self.credentialFactories = dict([(factory.scheme, factory)
+                                         for factory in credentialFactories])
+        self.loginInterfaces = loginInterfaces
+
+    def hook(self, req):
+        req.portal = self.portal
+        req.credentialFactories = self.credentialFactories
+        req.loginInterfaces = self.loginInterfaces
+
+
+class IPrincipal(Interface):
+    pass
+
+class DavRealm(object):
+    implements(portal.IRealm)
+
+    def requestAvatar(self, avatarId, mind, *interfaces):
+        if IPrincipal in interfaces:
+            return IPrincipal, davxml.Principal(davxml.HRef(avatarId[0])), davxml.Principal(davxml.HRef(avatarId[1]))
+        
+        raise NotImplementedError("Only IPrincipal interface is supported")
+
+
+class IPrincipalCredentials(Interface):
+    pass
+
+
+class PrincipalCredentials(object):
+    implements(IPrincipalCredentials)
+
+    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(object):
+    implements(checkers.ICredentialsChecker)
+
+    credentialInterfaces = (IPrincipalCredentials,)
+
+    def _cbPasswordMatch(self, matched, principalURIs):
+        if matched:
+            # We return both URIs
+            return principalURIs
+        else:
+            raise error.UnauthorizedLogin("Bad credentials for: %s" % (principalURIs[0],))
+
+    def requestAvatarId(self, credentials):
+        pcreds = IPrincipalCredentials(credentials)
+        pswd = str(pcreds.authnPrincipal.readDeadProperty(TwistedPasswordProperty))
+
+        d = defer.maybeDeferred(credentials.checkPassword, pswd)
+        d.addCallback(self._cbPasswordMatch, (pcreds.authnPrincipal.principalURL(), pcreds.authzPrincipal.principalURL()))
+        return d
+
+##
+# Utilities
+##
+
+class TwistedPasswordProperty (davxml.WebDAVTextElement):
+    namespace = twisted_private_namespace
+    name = "password"
+
+davxml.registerElement(TwistedPasswordProperty)

Modified: CalendarServer/trunk/twext/web2/dav/davxml.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/davxml.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/davxml.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,39 +1,92 @@
+
 ##
-# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Computer, 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
+# 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.
 #
-# 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: Wilfredo Sanchez, wsanchez at apple.com
 ##
 
 """
-Extensions to twisted.web2.dav.davxml
+WebDAV XML Support.
+
+This module provides XML utilities for use with WebDAV.
+
+This API is considered private to static.py and is therefore subject to
+change.
+
+See RFC 2518: http://www.ietf.org/rfc/rfc2518.txt (WebDAV)
+See RFC 3253: http://www.ietf.org/rfc/rfc3253.txt (WebDAV + Versioning)
+See RFC 3744: http://www.ietf.org/rfc/rfc3744.txt (WebDAV ACLs)
 """
 
-__all__ = [
-    "sname2qname",
-    "qname2sname",
-    "ErrorDescription",
-    "ErrorResponse",
-    "SyncCollection",
-    "SyncToken",
-]
+#
+# Import all XML element definitions
+#
 
-from twisted.web2.http import Response
-from twisted.web2.dav.http import ErrorResponse as SuperErrorResponse
-from twisted.web2.dav.davxml import dav_namespace, twisted_dav_namespace, WebDAVElement, WebDAVTextElement
-from twisted.web2.dav.davxml import WebDAVUnknownElement, Error
-from twisted.web2.http_headers import MimeType
+from twext.web2.dav.element.base    import *
+from twext.web2.dav.element.parser  import *
+from twext.web2.dav.element.util    import *
+from twext.web2.dav.element.rfc2518 import *
+from twext.web2.dav.element.rfc3253 import *
+from twext.web2.dav.element.rfc3744 import *
+from twext.web2.dav.element.rfc4331 import *
+from twext.web2.dav.element.extensions import CurrentUserPrincipal
 
+#
+# Register all XML elements with the parser
+#
 
+from twext.web2.dav.element import base as b
+from twext.web2.dav.element import parser as p
+from twext.web2.dav.element import util as u
+from twext.web2.dav.element import rfc2518 as r1
+from twext.web2.dav.element import rfc3253 as r2
+from twext.web2.dav.element import rfc3744 as r3
+from twext.web2.dav.element import extensions as e
+
+__all__ = (
+    registerElements(b) +
+    registerElements(p) +
+    registerElements(u) +
+    registerElements(r1) +
+    registerElements(r2) +
+    registerElements(r3) +
+    registerElements(e) +
+    [
+        "sname2qname",
+        "qname2sname",
+        "ErrorDescription",
+        "ErrorResponse",
+        "SyncCollection",
+        "SyncToken",
+    ]
+)
+
+#from twext.web2.http import Response
+
+from twext.web2.dav._errorbase import ErrorResponse as SuperErrorResponse
+from twext.web2.dav.davxml import dav_namespace, twisted_dav_namespace, WebDAVElement, WebDAVTextElement
+from twext.web2.dav.davxml import WebDAVUnknownElement, Error
+from twext.web2.http_headers import MimeType
+
+
 def sname2qname(sname):
     """
     Convert an sname into a qname.
@@ -115,12 +168,9 @@
         else:
             output = Error(error).toxml()
 
-        Response.__init__(self, code=code, stream=output)
+        SuperErrorResponse.__init__(self, code=code, error=error, stream=output)
 
-        self.headers.setHeader("content-type", MimeType("text", "xml"))
 
-        self.error = error
-
     def __repr__(self):
         return "<%s %s %s>" % (self.__class__.__name__, self.code, self.error.sname())
 
@@ -159,6 +209,8 @@
                     raise ValueError("Only one of DAV:prop allowed")
                 self.property = child
 
+registerElement(SyncCollection)
+
 class SyncToken (WebDAVTextElement):
     """
     Synchronization token used in report and as a property.
@@ -167,4 +219,6 @@
     hidden = True
     protected = True
 
+registerElement(SyncToken)
 
+

Deleted: CalendarServer/trunk/twext/web2/dav/element/__init__.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/__init__.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/element/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,42 +0,0 @@
-# Copyright (c) 2009 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-##
-# 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.
-#
-##
-
-"""
-WebDAV XML Glue.
-
-Modules in this package provide the implementation of twext.web2.dav.davxml.
-"""
-
-__all__ = [
-    "base",
-    "parser",
-    "util",
-    "rfc2518",
-    "rfc3253",
-    "rfc3744",
-    "rfc4331",
-    "extensions",
-]

Copied: CalendarServer/trunk/twext/web2/dav/element/__init__.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/__init__.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/element/__init__.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/element/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,42 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+##
+# 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.
+#
+##
+
+"""
+WebDAV XML Glue.
+
+Modules in this package provide the implementation of twext.web2.dav.davxml.
+"""
+
+__all__ = [
+    "base",
+    "parser",
+    "util",
+    "rfc2518",
+    "rfc3253",
+    "rfc3744",
+    "rfc4331",
+    "extensions",
+]

Deleted: CalendarServer/trunk/twext/web2/dav/element/base.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/base.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/element/base.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,733 +0,0 @@
-##
-# Copyright (c) 2007 Twisted Matrix Laboratories.
-# 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
-##
-
-"""
-WebDAV XML base classes.
-
-This module provides XML utilities for use with WebDAV.
-
-See RFC 2518: http://www.ietf.org/rfc/rfc2518.txt (WebDAV)
-"""
-
-__all__ = [
-    "dav_namespace",
-    "twisted_dav_namespace",
-    "twisted_private_namespace",
-    "WebDAVElement",
-    "PCDATAElement",
-    "WebDAVOneShotElement",
-    "WebDAVUnknownElement",
-    "WebDAVEmptyElement",
-    "WebDAVTextElement",
-    "WebDAVDateTimeElement",
-    "DateTimeHeaderElement",
-]
-
-import string
-import cStringIO as StringIO
-import xml.dom.minidom
-
-import datetime
-
-from twisted.python import log
-from twext.web2.http_headers import parseDateTime
-from twext.web2.dav.element.util import PrintXML, decodeXMLName
-
-##
-# Base XML elements
-##
-
-dav_namespace = "DAV:"
-twisted_dav_namespace = "http://twistedmatrix.com/xml_namespace/dav/"
-twisted_private_namespace = twisted_dav_namespace + "private/"
-
-class WebDAVElement (object):
-    """
-    WebDAV XML element. (RFC 2518, section 12)
-    """
-    namespace          = dav_namespace # Element namespace (class variable)
-    name               = None          # Element name (class variable)
-    allowed_children   = None          # Types & count limits on child elements
-    allowed_attributes = None          # Allowed attribute names
-    hidden             = False         # Don't list in PROPFIND with <allprop>
-    protected          = False         # See RFC 3253 section 1.4.1
-    unregistered       = False         # Subclass of factory; doesn't register
-
-    def qname(self):
-        return (self.namespace, self.name)
-
-    def sname(self):
-        return "{%s}%s" % (self.namespace, self.name)
-
-    qname = classmethod(qname)
-    sname = classmethod(sname)
-
-    def __init__(self, *children, **attributes):
-        super(WebDAVElement, self).__init__()
-
-        if self.allowed_children is None:
-            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
-        #
-        allowed_children = dict([
-            (child_type, list(limits))
-            for child_type, limits
-            in self.allowed_children.items()
-        ])
-
-        my_children = []
-
-        for child in children:
-
-            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):
-                    qname = allowed
-                elif child.qname() == allowed:
-                    qname = allowed
-                else:
-                    continue
-
-                if min is not None and min > 0:
-                    min -= 1
-                if max is not None:
-                    assert max > 0, "Too many children of type %s for %s" % (child.sname(), self.sname())
-                    max -= 1
-                allowed_children[qname] = (min, max)
-                my_children.append(child)
-                break
-            else:
-                if not (isinstance(child, PCDATAElement) and child.isWhitespace()):
-                    log.msg("Child of type %s is unexpected and therefore ignored in %s element"
-                            % (child.sname(), self.sname()))
-
-        for qname, (min, max) in allowed_children.items():
-            if min != 0:
-                raise ValueError("Not enough children of type {%s}%s for %s"
-                                 % (qname[0], qname[1], self.sname()))
-
-        self.children = tuple(my_children)
-
-        #
-        # Validate that attributes have known names
-        #
-        my_attributes = {}
-
-        if self.allowed_attributes:
-            for name in attributes:
-                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()))
-
-        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()
-
-    def __repr__(self):
-        if hasattr(self, "attributes") and hasattr(self, "children"):
-            return "<%s %r: %r>" % (self.sname(), self.attributes, self.children)
-        else:
-            return "<%s>" % (self.sname())
-
-    def __eq__(self, other):
-        if isinstance(other, WebDAVElement):
-            return (
-                self.name       == other.name       and
-                self.namespace  == other.namespace  and
-                self.attributes == other.attributes and
-                self.children   == other.children
-            )
-        else:
-            return NotImplemented
-
-    def __ne__(self, other):
-        return not self.__eq__(other)
-
-    def __contains__(self, child):
-        return child in self.children
-
-
-    def writeXML(self, output):
-        output.write("<?xml version='1.0' encoding='UTF-8'?>")
-        self.writeToStream(output, "", 0, True)
-
-    
-    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 str(output.getvalue())
-
-    def element(self, document):
-        element = document.createElementNS(self.namespace, self.name)
-        if hasattr(self, "attributes"):
-            for name, value in self.attributes.items():
-                namespace, name = decodeXMLName(name)
-                attribute = document.createAttributeNS(namespace, name)
-                attribute.nodeValue = value
-                element.setAttributeNodeNS(attribute)
-        return element
-
-    def addToDOM(self, document, parent):
-        element = self.element(document)
-
-        if parent is None:
-            document.appendChild(element)
-        else:
-            parent.appendChild(element)
-
-        for child in self.children:
-            if child:
-                try:
-                    child.addToDOM(document, element)
-                except:
-                    log.err("Unable to add child %r of element %s to DOM" % (child, self))
-                    raise
-
-    def childrenOfType(self, child_type):
-        """
-        Returns a list of children with the same qname as the given type.
-        """
-        if type(child_type) is tuple:
-            qname = child_type
-        else:
-            qname = child_type.qname()
-
-        return [ c for c in self.children if c.qname() == qname ]
-
-    def childOfType(self, child_type):
-        """
-        Returns a child of the given type, if any, or None.
-        Raises ValueError if more than one is found.
-        """
-        found = None
-        for child in self.childrenOfType(child_type):
-            if found:
-                raise ValueError("Multiple %s elements found in %s" % (child_type.sname(), self.toxml()))
-            found = child
-        return found
-
-    def removeWhitespaceNodes(self):
-        """ Removes all of the whitespace-only text decendants of a DOM node. """
-        # prepare the list of text nodes to remove (and recurse when needed)
-        remove_list = []
-        for child in self.children:
-            if isinstance(child, PCDATAElement) and not child.data.strip():
-                # add this text node to the to-be-removed list
-                remove_list.append(child)
-            elif isinstance(child, WebDAVElement):
-                # recurse, it's the simplest way to deal with the subtree
-                child.removeWhitespaceNodes()
-
-        # perform the removals
-        newchildren = []
-        for child in self.children:
-            if child not in remove_list:
-                newchildren.append(child)
-        self.children = tuple(newchildren)
-
-class PCDATAElement (object):
-    def sname(self): return "#PCDATA"
-
-    qname = classmethod(sname)
-    sname = classmethod(sname)
-
-    def __init__(self, data):
-        super(PCDATAElement, self).__init__()
-
-        if data is None:
-            data = ""
-        elif type(data) is unicode:
-            data = data.encode("utf-8")
-        else:
-            assert type(data) is str, ("PCDATA must be a string: %r" % (data,))
-
-        self.data = data
-
-    def validate(self):
-        pass
-
-    def __str__(self):
-        return str(self.data)
-
-    def __repr__(self):
-        return "<%s: %r>" % (self.__class__.__name__, self.data)
-
-    def __add__(self, other):
-        if isinstance(other, PCDATAElement):
-            return self.__class__(self.data + other.data)
-        else:
-            return self.__class__(self.data + other)
-
-    def __eq__(self, other):
-        if isinstance(other, PCDATAElement):
-            return self.data == other.data
-        elif type(other) in (str, unicode):
-            return self.data == other
-        else:
-            return NotImplemented
-
-    def __ne__(self, other):
-        return not self.__eq__(other)
-
-    def isWhitespace(self):
-        for char in str(self):
-            if char not in string.whitespace:
-                return False
-        return True
-
-    def element(self, document):
-        return document.createTextNode(self.data)
-
-    def addToDOM(self, document, parent):
-        try:
-            parent.appendChild(self.element(document))
-        except TypeError:
-            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.
-    """
-    __singletons = {}
-
-    def __new__(clazz, *children):
-        child = None
-        for next in children:
-            if isinstance(next, WebDAVEmptyElement):
-                if child is not None:
-                    raise ValueError("%s must have exactly one child, not %r"
-                                     % (clazz.__name__, children))
-                child = next
-            elif isinstance(next, PCDATAElement):
-                pass
-            else:
-                raise ValueError("%s child is not a WebDAVEmptyElement instance: %s"
-                                 % (clazz.__name__, next))
-
-        if clazz not in WebDAVOneShotElement.__singletons:
-            WebDAVOneShotElement.__singletons[clazz] = {
-                child: WebDAVElement.__new__(clazz)
-            }
-        elif child not in WebDAVOneShotElement.__singletons[clazz]:
-            WebDAVOneShotElement.__singletons[clazz][child] = (
-                WebDAVElement.__new__(clazz)
-            )
-
-        return WebDAVOneShotElement.__singletons[clazz][child]
-
-class WebDAVUnknownElement (WebDAVElement):
-    """
-    Placeholder for unknown element tag names.
-    """
-    allowed_children = {
-        WebDAVElement: (0, None),
-        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.
-    """
-    __singletons = {}
-
-    def __new__(clazz, *args, **kwargs):
-        assert not args
-
-        if kwargs:
-            return WebDAVElement.__new__(clazz)
-        else:
-            if clazz not in WebDAVEmptyElement.__singletons:
-                WebDAVEmptyElement.__singletons[clazz] = (WebDAVElement.__new__(clazz))
-            return WebDAVEmptyElement.__singletons[clazz]
-
-    allowed_children = {}
-
-    children = ()
-
-
-    def __hash__(self):
-        """
-        Define a hash method, so that an empty element can serve as dictionary
-        keys. It's mainly useful to define singletons with
-        L{WebDAVOneShotElement}.
-        """
-        return hash((self.name, self.namespace))
-
-
-
-class WebDAVTextElement (WebDAVElement):
-    """
-    WebDAV element containing PCDATA.
-    """
-    @classmethod
-    def fromString(clazz, string):
-        if string is None:
-            return clazz()
-        elif isinstance(string, (str, unicode)):
-            return clazz(PCDATAElement(string))
-        else:
-            return clazz(PCDATAElement(str(string)))
-
-    allowed_children = { PCDATAElement: (0, None) }
-
-    def __str__(self):
-        return "".join([c.data for c in self.children])
-
-    def __repr__(self):
-        content = str(self)
-        if content:
-            return "<%s: %r>" % (self.sname(), content)
-        else:
-            return "<%s>" % (self.sname(),)
-
-    def __eq__(self, other):
-        if isinstance(other, WebDAVTextElement):
-            return str(self) == str(other)
-        elif type(other) in (str, unicode):
-            return str(self) == other
-        else:
-            return NotImplemented
-
-class WebDAVDateTimeElement (WebDAVTextElement):
-    """
-    WebDAV date-time element. (RFC 2518, section 23.2)
-    """
-    def fromDate(clazz, date):
-        """
-        date may be a datetime.datetime instance, a POSIX timestamp
-        (integer value, such as returned by time.time()), or an ISO
-        8601-formatted (eg. "2005-06-13T16:14:11Z") date/time string.
-        """
-        def isoformat(date):
-            if date.utcoffset() is None:
-                return date.isoformat() + "Z"
-            else:
-                return date.isoformat()
-
-        if type(date) is int:
-            date = isoformat(datetime.datetime.fromtimestamp(date))
-        elif type(date) is str:
-            pass
-        elif type(date) is unicode:
-            date = date.encode("utf-8")
-        elif isinstance(date, datetime.datetime):
-            date = isoformat(date)
-        else:
-            raise ValueError("Unknown date type: %r" % (date,))
-
-        return clazz(PCDATAElement(date))
-
-    fromDate = classmethod(fromDate)
-
-    def __init__(self, *children, **attributes):
-        super(WebDAVDateTimeElement, self).__init__(*children, **attributes)
-        self.datetime() # Raise ValueError if the format is wrong
-
-    def __eq__(self, other):
-        if isinstance(other, WebDAVDateTimeElement):
-            return self.datetime() == other.datetime()
-        else:
-            return NotImplemented
-
-    def datetime(self):
-        s = str(self)
-        if not s:
-            return None
-        else:
-            return parse_date(s)
-
-class DateTimeHeaderElement (WebDAVTextElement):
-    """
-    WebDAV date-time element for elements that substitute for HTTP
-    headers. (RFC 2068, section 3.3.1)
-    """
-    def fromDate(clazz, date):
-        """
-        date may be a datetime.datetime instance, a POSIX timestamp
-        (integer value, such as returned by time.time()), or an RFC
-        2068 Full Date (eg. "Mon, 23 May 2005 04:52:22 GMT") string.
-        """
-        def format(date):
-            #
-            # FIXME: strftime() is subject to localization nonsense; we need to
-            # ensure that we're using the correct localization, or don't use
-            # strftime().
-            #
-            return date.strftime("%a, %d %b %Y %H:%M:%S GMT")
-
-        if type(date) is int:
-            date = format(datetime.datetime.fromtimestamp(date))
-        elif type(date) is str:
-            pass
-        elif type(date) is unicode:
-            date = date.encode("utf-8")
-        elif isinstance(date, datetime.datetime):
-            if date.tzinfo:
-                raise NotImplementedError("I need to normalize to UTC")
-            date = format(date)
-        else:
-            raise ValueError("Unknown date type: %r" % (date,))
-
-        return clazz(PCDATAElement(date))
-
-    fromDate = classmethod(fromDate)
-
-    def __init__(self, *children, **attributes):
-        super(DateTimeHeaderElement, self).__init__(*children, **attributes)
-        self.datetime() # Raise ValueError if the format is wrong
-
-    def __eq__(self, other):
-        if isinstance(other, WebDAVDateTimeElement):
-            return self.datetime() == other.datetime()
-        else:
-            return NotImplemented
-
-    def datetime(self):
-        s = str(self)
-        if not s:
-            return None
-        else:
-            return parseDateTime(s)
-
-##
-# Utilities
-##
-
-class FixedOffset (datetime.tzinfo):
-    """
-    Fixed offset in minutes east from UTC.
-    """
-    def __init__(self, offset, name=None):
-        super(FixedOffset, self).__init__()
-
-        self._offset = datetime.timedelta(minutes=offset)
-        self._name   = name
-
-    def utcoffset(self, dt): return self._offset
-    def tzname   (self, dt): return self._name
-    def dst      (self, dt): return datetime.timedelta(0)
-
-def parse_date(date):
-    """
-    Parse an ISO 8601 date and return a corresponding datetime.datetime object.
-    """
-    # See http://www.iso.org/iso/en/prods-services/popstds/datesandtime.html
-
-    global regex_date
-
-    if regex_date is None:
-        import re
-
-        regex_date = re.compile(
-            "^" +
-              "(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})T" +
-              "(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})(?:.(?P<subsecond>\d+))*" +
-              "(?:Z|(?P<offset_sign>\+|-)(?P<offset_hour>\d{2}):(?P<offset_minute>\d{2}))" +
-            "$"
-        )
-
-    match = regex_date.match(date)
-    if match is not None:
-        subsecond = match.group("subsecond")
-        if subsecond is None:
-            subsecond = 0
-        else:
-            subsecond = int(subsecond)
-
-        offset_sign = match.group("offset_sign")
-        if offset_sign is None:
-            offset = FixedOffset(0)
-        else:
-            offset_hour   = int(match.group("offset_hour"  ))
-            offset_minute = int(match.group("offset_minute"))
-
-            delta = (offset_hour * 60) + offset_minute
-
-            if   offset_sign == "+": offset = FixedOffset(0 - delta)
-            elif offset_sign == "-": offset = FixedOffset(0 + delta)
-
-        return datetime.datetime(
-            int(match.group("year"  )),
-            int(match.group("month" )),
-            int(match.group("day"   )),
-            int(match.group("hour"  )),
-            int(match.group("minute")),
-            int(match.group("second")),
-            subsecond,
-            offset
-        )
-    else:
-        raise ValueError("Invalid ISO 8601 date format: %r" % (date,))
-
-regex_date = None

Copied: CalendarServer/trunk/twext/web2/dav/element/base.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/base.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/element/base.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/element/base.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,733 @@
+##
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# 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
+##
+
+"""
+WebDAV XML base classes.
+
+This module provides XML utilities for use with WebDAV.
+
+See RFC 2518: http://www.ietf.org/rfc/rfc2518.txt (WebDAV)
+"""
+
+__all__ = [
+    "dav_namespace",
+    "twisted_dav_namespace",
+    "twisted_private_namespace",
+    "WebDAVElement",
+    "PCDATAElement",
+    "WebDAVOneShotElement",
+    "WebDAVUnknownElement",
+    "WebDAVEmptyElement",
+    "WebDAVTextElement",
+    "WebDAVDateTimeElement",
+    "DateTimeHeaderElement",
+]
+
+import string
+import cStringIO as StringIO
+import xml.dom.minidom
+
+import datetime
+
+from twisted.python import log
+from twext.web2.http_headers import parseDateTime
+from twext.web2.dav.element.util import PrintXML, decodeXMLName
+
+##
+# Base XML elements
+##
+
+dav_namespace = "DAV:"
+twisted_dav_namespace = "http://twistedmatrix.com/xml_namespace/dav/"
+twisted_private_namespace = twisted_dav_namespace + "private/"
+
+class WebDAVElement (object):
+    """
+    WebDAV XML element. (RFC 2518, section 12)
+    """
+    namespace          = dav_namespace # Element namespace (class variable)
+    name               = None          # Element name (class variable)
+    allowed_children   = None          # Types & count limits on child elements
+    allowed_attributes = None          # Allowed attribute names
+    hidden             = False         # Don't list in PROPFIND with <allprop>
+    protected          = False         # See RFC 3253 section 1.4.1
+    unregistered       = False         # Subclass of factory; doesn't register
+
+    def qname(self):
+        return (self.namespace, self.name)
+
+    def sname(self):
+        return "{%s}%s" % (self.namespace, self.name)
+
+    qname = classmethod(qname)
+    sname = classmethod(sname)
+
+    def __init__(self, *children, **attributes):
+        super(WebDAVElement, self).__init__()
+
+        if self.allowed_children is None:
+            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
+        #
+        allowed_children = dict([
+            (child_type, list(limits))
+            for child_type, limits
+            in self.allowed_children.items()
+        ])
+
+        my_children = []
+
+        for child in children:
+
+            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):
+                    qname = allowed
+                elif child.qname() == allowed:
+                    qname = allowed
+                else:
+                    continue
+
+                if min is not None and min > 0:
+                    min -= 1
+                if max is not None:
+                    assert max > 0, "Too many children of type %s for %s" % (child.sname(), self.sname())
+                    max -= 1
+                allowed_children[qname] = (min, max)
+                my_children.append(child)
+                break
+            else:
+                if not (isinstance(child, PCDATAElement) and child.isWhitespace()):
+                    log.msg("Child of type %s is unexpected and therefore ignored in %s element"
+                            % (child.sname(), self.sname()))
+
+        for qname, (min, max) in allowed_children.items():
+            if min != 0:
+                raise ValueError("Not enough children of type {%s}%s for %s"
+                                 % (qname[0], qname[1], self.sname()))
+
+        self.children = tuple(my_children)
+
+        #
+        # Validate that attributes have known names
+        #
+        my_attributes = {}
+
+        if self.allowed_attributes:
+            for name in attributes:
+                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()))
+
+        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()
+
+    def __repr__(self):
+        if hasattr(self, "attributes") and hasattr(self, "children"):
+            return "<%s %r: %r>" % (self.sname(), self.attributes, self.children)
+        else:
+            return "<%s>" % (self.sname())
+
+    def __eq__(self, other):
+        if isinstance(other, WebDAVElement):
+            return (
+                self.name       == other.name       and
+                self.namespace  == other.namespace  and
+                self.attributes == other.attributes and
+                self.children   == other.children
+            )
+        else:
+            return NotImplemented
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __contains__(self, child):
+        return child in self.children
+
+
+    def writeXML(self, output):
+        output.write("<?xml version='1.0' encoding='UTF-8'?>")
+        self.writeToStream(output, "", 0, True)
+
+    
+    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 str(output.getvalue())
+
+    def element(self, document):
+        element = document.createElementNS(self.namespace, self.name)
+        if hasattr(self, "attributes"):
+            for name, value in self.attributes.items():
+                namespace, name = decodeXMLName(name)
+                attribute = document.createAttributeNS(namespace, name)
+                attribute.nodeValue = value
+                element.setAttributeNodeNS(attribute)
+        return element
+
+    def addToDOM(self, document, parent):
+        element = self.element(document)
+
+        if parent is None:
+            document.appendChild(element)
+        else:
+            parent.appendChild(element)
+
+        for child in self.children:
+            if child:
+                try:
+                    child.addToDOM(document, element)
+                except:
+                    log.err("Unable to add child %r of element %s to DOM" % (child, self))
+                    raise
+
+    def childrenOfType(self, child_type):
+        """
+        Returns a list of children with the same qname as the given type.
+        """
+        if type(child_type) is tuple:
+            qname = child_type
+        else:
+            qname = child_type.qname()
+
+        return [ c for c in self.children if c.qname() == qname ]
+
+    def childOfType(self, child_type):
+        """
+        Returns a child of the given type, if any, or None.
+        Raises ValueError if more than one is found.
+        """
+        found = None
+        for child in self.childrenOfType(child_type):
+            if found:
+                raise ValueError("Multiple %s elements found in %s" % (child_type.sname(), self.toxml()))
+            found = child
+        return found
+
+    def removeWhitespaceNodes(self):
+        """ Removes all of the whitespace-only text decendants of a DOM node. """
+        # prepare the list of text nodes to remove (and recurse when needed)
+        remove_list = []
+        for child in self.children:
+            if isinstance(child, PCDATAElement) and not child.data.strip():
+                # add this text node to the to-be-removed list
+                remove_list.append(child)
+            elif isinstance(child, WebDAVElement):
+                # recurse, it's the simplest way to deal with the subtree
+                child.removeWhitespaceNodes()
+
+        # perform the removals
+        newchildren = []
+        for child in self.children:
+            if child not in remove_list:
+                newchildren.append(child)
+        self.children = tuple(newchildren)
+
+class PCDATAElement (object):
+    def sname(self): return "#PCDATA"
+
+    qname = classmethod(sname)
+    sname = classmethod(sname)
+
+    def __init__(self, data):
+        super(PCDATAElement, self).__init__()
+
+        if data is None:
+            data = ""
+        elif type(data) is unicode:
+            data = data.encode("utf-8")
+        else:
+            assert type(data) is str, ("PCDATA must be a string: %r" % (data,))
+
+        self.data = data
+
+    def validate(self):
+        pass
+
+    def __str__(self):
+        return str(self.data)
+
+    def __repr__(self):
+        return "<%s: %r>" % (self.__class__.__name__, self.data)
+
+    def __add__(self, other):
+        if isinstance(other, PCDATAElement):
+            return self.__class__(self.data + other.data)
+        else:
+            return self.__class__(self.data + other)
+
+    def __eq__(self, other):
+        if isinstance(other, PCDATAElement):
+            return self.data == other.data
+        elif type(other) in (str, unicode):
+            return self.data == other
+        else:
+            return NotImplemented
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def isWhitespace(self):
+        for char in str(self):
+            if char not in string.whitespace:
+                return False
+        return True
+
+    def element(self, document):
+        return document.createTextNode(self.data)
+
+    def addToDOM(self, document, parent):
+        try:
+            parent.appendChild(self.element(document))
+        except TypeError:
+            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.
+    """
+    __singletons = {}
+
+    def __new__(clazz, *children):
+        child = None
+        for next in children:
+            if isinstance(next, WebDAVEmptyElement):
+                if child is not None:
+                    raise ValueError("%s must have exactly one child, not %r"
+                                     % (clazz.__name__, children))
+                child = next
+            elif isinstance(next, PCDATAElement):
+                pass
+            else:
+                raise ValueError("%s child is not a WebDAVEmptyElement instance: %s"
+                                 % (clazz.__name__, next))
+
+        if clazz not in WebDAVOneShotElement.__singletons:
+            WebDAVOneShotElement.__singletons[clazz] = {
+                child: WebDAVElement.__new__(clazz)
+            }
+        elif child not in WebDAVOneShotElement.__singletons[clazz]:
+            WebDAVOneShotElement.__singletons[clazz][child] = (
+                WebDAVElement.__new__(clazz)
+            )
+
+        return WebDAVOneShotElement.__singletons[clazz][child]
+
+class WebDAVUnknownElement (WebDAVElement):
+    """
+    Placeholder for unknown element tag names.
+    """
+    allowed_children = {
+        WebDAVElement: (0, None),
+        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.
+    """
+    __singletons = {}
+
+    def __new__(clazz, *args, **kwargs):
+        assert not args
+
+        if kwargs:
+            return WebDAVElement.__new__(clazz)
+        else:
+            if clazz not in WebDAVEmptyElement.__singletons:
+                WebDAVEmptyElement.__singletons[clazz] = (WebDAVElement.__new__(clazz))
+            return WebDAVEmptyElement.__singletons[clazz]
+
+    allowed_children = {}
+
+    children = ()
+
+
+    def __hash__(self):
+        """
+        Define a hash method, so that an empty element can serve as dictionary
+        keys. It's mainly useful to define singletons with
+        L{WebDAVOneShotElement}.
+        """
+        return hash((self.name, self.namespace))
+
+
+
+class WebDAVTextElement (WebDAVElement):
+    """
+    WebDAV element containing PCDATA.
+    """
+    @classmethod
+    def fromString(clazz, string):
+        if string is None:
+            return clazz()
+        elif isinstance(string, (str, unicode)):
+            return clazz(PCDATAElement(string))
+        else:
+            return clazz(PCDATAElement(str(string)))
+
+    allowed_children = { PCDATAElement: (0, None) }
+
+    def __str__(self):
+        return "".join([c.data for c in self.children])
+
+    def __repr__(self):
+        content = str(self)
+        if content:
+            return "<%s: %r>" % (self.sname(), content)
+        else:
+            return "<%s>" % (self.sname(),)
+
+    def __eq__(self, other):
+        if isinstance(other, WebDAVTextElement):
+            return str(self) == str(other)
+        elif type(other) in (str, unicode):
+            return str(self) == other
+        else:
+            return NotImplemented
+
+class WebDAVDateTimeElement (WebDAVTextElement):
+    """
+    WebDAV date-time element. (RFC 2518, section 23.2)
+    """
+    def fromDate(clazz, date):
+        """
+        date may be a datetime.datetime instance, a POSIX timestamp
+        (integer value, such as returned by time.time()), or an ISO
+        8601-formatted (eg. "2005-06-13T16:14:11Z") date/time string.
+        """
+        def isoformat(date):
+            if date.utcoffset() is None:
+                return date.isoformat() + "Z"
+            else:
+                return date.isoformat()
+
+        if type(date) is int:
+            date = isoformat(datetime.datetime.fromtimestamp(date))
+        elif type(date) is str:
+            pass
+        elif type(date) is unicode:
+            date = date.encode("utf-8")
+        elif isinstance(date, datetime.datetime):
+            date = isoformat(date)
+        else:
+            raise ValueError("Unknown date type: %r" % (date,))
+
+        return clazz(PCDATAElement(date))
+
+    fromDate = classmethod(fromDate)
+
+    def __init__(self, *children, **attributes):
+        super(WebDAVDateTimeElement, self).__init__(*children, **attributes)
+        self.datetime() # Raise ValueError if the format is wrong
+
+    def __eq__(self, other):
+        if isinstance(other, WebDAVDateTimeElement):
+            return self.datetime() == other.datetime()
+        else:
+            return NotImplemented
+
+    def datetime(self):
+        s = str(self)
+        if not s:
+            return None
+        else:
+            return parse_date(s)
+
+class DateTimeHeaderElement (WebDAVTextElement):
+    """
+    WebDAV date-time element for elements that substitute for HTTP
+    headers. (RFC 2068, section 3.3.1)
+    """
+    def fromDate(clazz, date):
+        """
+        date may be a datetime.datetime instance, a POSIX timestamp
+        (integer value, such as returned by time.time()), or an RFC
+        2068 Full Date (eg. "Mon, 23 May 2005 04:52:22 GMT") string.
+        """
+        def format(date):
+            #
+            # FIXME: strftime() is subject to localization nonsense; we need to
+            # ensure that we're using the correct localization, or don't use
+            # strftime().
+            #
+            return date.strftime("%a, %d %b %Y %H:%M:%S GMT")
+
+        if type(date) is int:
+            date = format(datetime.datetime.fromtimestamp(date))
+        elif type(date) is str:
+            pass
+        elif type(date) is unicode:
+            date = date.encode("utf-8")
+        elif isinstance(date, datetime.datetime):
+            if date.tzinfo:
+                raise NotImplementedError("I need to normalize to UTC")
+            date = format(date)
+        else:
+            raise ValueError("Unknown date type: %r" % (date,))
+
+        return clazz(PCDATAElement(date))
+
+    fromDate = classmethod(fromDate)
+
+    def __init__(self, *children, **attributes):
+        super(DateTimeHeaderElement, self).__init__(*children, **attributes)
+        self.datetime() # Raise ValueError if the format is wrong
+
+    def __eq__(self, other):
+        if isinstance(other, WebDAVDateTimeElement):
+            return self.datetime() == other.datetime()
+        else:
+            return NotImplemented
+
+    def datetime(self):
+        s = str(self)
+        if not s:
+            return None
+        else:
+            return parseDateTime(s)
+
+##
+# Utilities
+##
+
+class FixedOffset (datetime.tzinfo):
+    """
+    Fixed offset in minutes east from UTC.
+    """
+    def __init__(self, offset, name=None):
+        super(FixedOffset, self).__init__()
+
+        self._offset = datetime.timedelta(minutes=offset)
+        self._name   = name
+
+    def utcoffset(self, dt): return self._offset
+    def tzname   (self, dt): return self._name
+    def dst      (self, dt): return datetime.timedelta(0)
+
+def parse_date(date):
+    """
+    Parse an ISO 8601 date and return a corresponding datetime.datetime object.
+    """
+    # See http://www.iso.org/iso/en/prods-services/popstds/datesandtime.html
+
+    global regex_date
+
+    if regex_date is None:
+        import re
+
+        regex_date = re.compile(
+            "^" +
+              "(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})T" +
+              "(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})(?:.(?P<subsecond>\d+))*" +
+              "(?:Z|(?P<offset_sign>\+|-)(?P<offset_hour>\d{2}):(?P<offset_minute>\d{2}))" +
+            "$"
+        )
+
+    match = regex_date.match(date)
+    if match is not None:
+        subsecond = match.group("subsecond")
+        if subsecond is None:
+            subsecond = 0
+        else:
+            subsecond = int(subsecond)
+
+        offset_sign = match.group("offset_sign")
+        if offset_sign is None:
+            offset = FixedOffset(0)
+        else:
+            offset_hour   = int(match.group("offset_hour"  ))
+            offset_minute = int(match.group("offset_minute"))
+
+            delta = (offset_hour * 60) + offset_minute
+
+            if   offset_sign == "+": offset = FixedOffset(0 - delta)
+            elif offset_sign == "-": offset = FixedOffset(0 + delta)
+
+        return datetime.datetime(
+            int(match.group("year"  )),
+            int(match.group("month" )),
+            int(match.group("day"   )),
+            int(match.group("hour"  )),
+            int(match.group("minute")),
+            int(match.group("second")),
+            subsecond,
+            offset
+        )
+    else:
+        raise ValueError("Invalid ISO 8601 date format: %r" % (date,))
+
+regex_date = None

Deleted: CalendarServer/trunk/twext/web2/dav/element/extensions.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/extensions.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/element/extensions.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,45 +0,0 @@
-# Copyright (c) 2009 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-##
-# 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.
-#
-##
-
-"""
-Implementation of draft-sanchez-webdav-current-principal-02.
-"""
-
-__all__ = ['CurrentUserPrincipal']
-
-from twext.web2.dav.element.base import WebDAVElement, dav_namespace
-
-
-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/twext/web2/dav/element/extensions.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/extensions.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/element/extensions.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/element/extensions.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,45 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+##
+# 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.
+#
+##
+
+"""
+Implementation of draft-sanchez-webdav-current-principal-02.
+"""
+
+__all__ = ['CurrentUserPrincipal']
+
+from twext.web2.dav.element.base import WebDAVElement, dav_namespace
+
+
+class CurrentUserPrincipal(WebDAVElement):
+    """
+    Current principal information
+    """
+    name = "current-user-principal"
+
+    allowed_children = {
+        (dav_namespace, "href" )                : (0, 1),
+        (dav_namespace, "unauthenticated" )     : (0, 1),
+    }

Deleted: CalendarServer/trunk/twext/web2/dav/element/parser.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/parser.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/element/parser.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,253 +0,0 @@
-##
-# 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
-##
-
-"""
-WebDAV XML parsing.
-
-This module provides XML utilities for use with WebDAV.
-
-See RFC 2518: http://www.ietf.org/rfc/rfc2518.txt (WebDAV)
-"""
-
-__all__ = [
-    "registerElement",
-    "registerElements",
-    "lookupElement",
-    "WebDAVDocument",
-]
-
-import cStringIO as StringIO
-import xml.dom.minidom
-import xml.sax
-
-from twext.web2.dav.element.base import WebDAVElement, WebDAVUnknownElement, PCDATAElement
-from twext.web2.dav.element.util import PrintXML
-
-##
-# Parsing
-##
-
-def registerElements(module):
-    """
-    Register XML elements defined in the given module with the parser.
-    """
-    element_names = []
-
-    items = module.__all__ if hasattr(module, "__all__") else dir(module)
-    for element_class_name in items:
-        element_class = getattr(module, element_class_name)
-
-        if type(element_class) is type and issubclass(element_class, WebDAVElement):
-            if element_class.namespace is None: continue
-            if element_class.name is None: continue
-            if element_class.unregistered: continue
-
-            registerElement(element_class, element_names)
-
-    return element_names
-
-def registerElement(element_class, element_names=None):
-    """
-    Register the supplied XML elements with the parser.
-    """
-    qname = element_class.namespace, element_class.name
-    
-    if qname in elements_by_tag_name:
-        raise AssertionError(
-            "Attempting to register qname %s multiple times: (%r, %r)"
-            % (qname, elements_by_tag_name[qname], element_class)
-        )
-    
-    if not (qname in elements_by_tag_name and issubclass(element_class, elements_by_tag_name[qname])):
-        elements_by_tag_name[qname] = element_class
-        if element_names is not None:
-            element_names.append(element_class.__name__)
-
-def lookupElement(qname):
-    """
-    Return the element class for the element with the given qname.
-    """
-    return elements_by_tag_name[qname]
-
-elements_by_tag_name = {}
-
-class WebDAVContentHandler (xml.sax.handler.ContentHandler):
-    def setDocumentLocator(self, locator): self.locator = locator
-    locator = None
-
-    def location(self):
-        return "line %d, column %d" % (self.locator.getLineNumber(), self.locator.getColumnNumber())
-
-    def startDocument(self):
-        self.stack = [{
-            "name"       : None,
-            "class"      : None,
-            "attributes" : None,
-            "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]
-
-        assert top["name"] is None
-        assert top["class"] is None
-        assert top["attributes"] is None
-        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 = {}
-
-        if attributes.getLength() is not 0:
-            for attr_name in attributes.getQNames():
-                attributes_dict[attr_name.encode("utf-8")] = attributes.getValueByQName(attr_name)
-
-        tag_namespace, tag_name = name
-
-        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:
-            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,
-            "class"      : element_class,
-            "attributes" : attributes_dict,
-            "children"   : [],
-        })
-
-    def endElementNS(self, name, qname):
-        # Pop the current element from the stack...
-        top = self.stack[-1]
-        del(self.stack[-1])
-
-        assert top["name"] == name, "Last item on stack is %s while closing %s" % (top["name"], name)
-
-        # ...then instantiate the element and add it to the parent's list of
-        # children.
-        try:
-            element = top["class"](*top["children"], **top["attributes"])
-        except ValueError, e:
-            e.args = ("%s at %s" % (e.args[0], self.location()),) + e.args[1:]
-            raise # Re-raises modified e, but preserves traceback
-
-        self.stack[-1]["children"].append(element)
-
-    def characters(self, 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)
-
-    def startElement(self, name, attributes):
-        raise AssertionError("startElement() should not be called by namespace-aware parser")
-
-    def endElement(self, name):
-        raise AssertionError("endElement() should not be called by namespace-aware parser")
-
-    def processingInstruction(self, target, data):
-        raise AssertionError("processing instructions are not allowed")
-
-    def skippedEntity(self, name):
-        raise AssertionError("skipped entities are not allowed")
-
-class WebDAVDocument (object):
-    """
-    WebDAV XML document.
-    """
-    def _parse(source_is_string):
-        def parse(source):
-            handler = WebDAVContentHandler()
-            parser  = xml.sax.make_parser()
-
-            parser.setContentHandler(handler)
-            parser.setFeature(xml.sax.handler.feature_namespaces, True)
-
-            if source_is_string: source = StringIO.StringIO(source)
-
-            try:
-                parser.parse(source)
-            except xml.sax.SAXParseException, e:
-                raise ValueError(e)
-
-            #handler.dom.root_element.validate()
-
-            return handler.dom
-
-        return parse
-        
-    fromStream = staticmethod(_parse(False))
-    fromString = staticmethod(_parse(True ))
-
-    def __init__(self, root_element):
-        """
-        root_element must be a WebDAVElement instance.
-        """
-        super(WebDAVDocument, self).__init__()
-
-        if not isinstance(root_element, WebDAVElement):
-            raise ValueError("Not a WebDAVElement: %r" % (root_element,))
-
-        self.root_element = root_element
-
-    def __str__(self):
-        output = StringIO.StringIO()
-        self.writeXML(output)
-        return output.getvalue()
-
-    def __eq__(self, other):
-        if isinstance(other, WebDAVDocument):
-            return self.root_element == other.root_element
-        else:
-            return NotImplemented
-
-    def writeXML(self, output):
-        document = xml.dom.minidom.Document()
-        self.root_element.addToDOM(document, None)
-        PrintXML(document, stream=output)
-
-    def toxml(self):
-        output = StringIO.StringIO()
-        self.writeXML(output)
-        return output.getvalue()

Copied: CalendarServer/trunk/twext/web2/dav/element/parser.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/parser.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/element/parser.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/element/parser.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,253 @@
+##
+# 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
+##
+
+"""
+WebDAV XML parsing.
+
+This module provides XML utilities for use with WebDAV.
+
+See RFC 2518: http://www.ietf.org/rfc/rfc2518.txt (WebDAV)
+"""
+
+__all__ = [
+    "registerElement",
+    "registerElements",
+    "lookupElement",
+    "WebDAVDocument",
+]
+
+import cStringIO as StringIO
+import xml.dom.minidom
+import xml.sax
+
+from twext.web2.dav.element.base import WebDAVElement, WebDAVUnknownElement, PCDATAElement
+from twext.web2.dav.element.util import PrintXML
+
+##
+# Parsing
+##
+
+def registerElements(module):
+    """
+    Register XML elements defined in the given module with the parser.
+    """
+    element_names = []
+
+    items = module.__all__ if hasattr(module, "__all__") else dir(module)
+    for element_class_name in items:
+        element_class = getattr(module, element_class_name)
+
+        if type(element_class) is type and issubclass(element_class, WebDAVElement):
+            if element_class.namespace is None: continue
+            if element_class.name is None: continue
+            if element_class.unregistered: continue
+
+            registerElement(element_class, element_names)
+
+    return element_names
+
+def registerElement(element_class, element_names=None):
+    """
+    Register the supplied XML elements with the parser.
+    """
+    qname = element_class.namespace, element_class.name
+    
+    if qname in elements_by_tag_name:
+        raise AssertionError(
+            "Attempting to register qname %s multiple times: (%r, %r)"
+            % (qname, elements_by_tag_name[qname], element_class)
+        )
+    
+    if not (qname in elements_by_tag_name and issubclass(element_class, elements_by_tag_name[qname])):
+        elements_by_tag_name[qname] = element_class
+        if element_names is not None:
+            element_names.append(element_class.__name__)
+
+def lookupElement(qname):
+    """
+    Return the element class for the element with the given qname.
+    """
+    return elements_by_tag_name[qname]
+
+elements_by_tag_name = {}
+
+class WebDAVContentHandler (xml.sax.handler.ContentHandler):
+    def setDocumentLocator(self, locator): self.locator = locator
+    locator = None
+
+    def location(self):
+        return "line %d, column %d" % (self.locator.getLineNumber(), self.locator.getColumnNumber())
+
+    def startDocument(self):
+        self.stack = [{
+            "name"       : None,
+            "class"      : None,
+            "attributes" : None,
+            "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]
+
+        assert top["name"] is None
+        assert top["class"] is None
+        assert top["attributes"] is None
+        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 = {}
+
+        if attributes.getLength() is not 0:
+            for attr_name in attributes.getQNames():
+                attributes_dict[attr_name.encode("utf-8")] = attributes.getValueByQName(attr_name)
+
+        tag_namespace, tag_name = name
+
+        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:
+            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,
+            "class"      : element_class,
+            "attributes" : attributes_dict,
+            "children"   : [],
+        })
+
+    def endElementNS(self, name, qname):
+        # Pop the current element from the stack...
+        top = self.stack[-1]
+        del(self.stack[-1])
+
+        assert top["name"] == name, "Last item on stack is %s while closing %s" % (top["name"], name)
+
+        # ...then instantiate the element and add it to the parent's list of
+        # children.
+        try:
+            element = top["class"](*top["children"], **top["attributes"])
+        except ValueError, e:
+            e.args = ("%s at %s" % (e.args[0], self.location()),) + e.args[1:]
+            raise # Re-raises modified e, but preserves traceback
+
+        self.stack[-1]["children"].append(element)
+
+    def characters(self, 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)
+
+    def startElement(self, name, attributes):
+        raise AssertionError("startElement() should not be called by namespace-aware parser")
+
+    def endElement(self, name):
+        raise AssertionError("endElement() should not be called by namespace-aware parser")
+
+    def processingInstruction(self, target, data):
+        raise AssertionError("processing instructions are not allowed")
+
+    def skippedEntity(self, name):
+        raise AssertionError("skipped entities are not allowed")
+
+class WebDAVDocument (object):
+    """
+    WebDAV XML document.
+    """
+    def _parse(source_is_string):
+        def parse(source):
+            handler = WebDAVContentHandler()
+            parser  = xml.sax.make_parser()
+
+            parser.setContentHandler(handler)
+            parser.setFeature(xml.sax.handler.feature_namespaces, True)
+
+            if source_is_string: source = StringIO.StringIO(source)
+
+            try:
+                parser.parse(source)
+            except xml.sax.SAXParseException, e:
+                raise ValueError(e)
+
+            #handler.dom.root_element.validate()
+
+            return handler.dom
+
+        return parse
+        
+    fromStream = staticmethod(_parse(False))
+    fromString = staticmethod(_parse(True ))
+
+    def __init__(self, root_element):
+        """
+        root_element must be a WebDAVElement instance.
+        """
+        super(WebDAVDocument, self).__init__()
+
+        if not isinstance(root_element, WebDAVElement):
+            raise ValueError("Not a WebDAVElement: %r" % (root_element,))
+
+        self.root_element = root_element
+
+    def __str__(self):
+        output = StringIO.StringIO()
+        self.writeXML(output)
+        return output.getvalue()
+
+    def __eq__(self, other):
+        if isinstance(other, WebDAVDocument):
+            return self.root_element == other.root_element
+        else:
+            return NotImplemented
+
+    def writeXML(self, output):
+        document = xml.dom.minidom.Document()
+        self.root_element.addToDOM(document, None)
+        PrintXML(document, stream=output)
+
+    def toxml(self):
+        output = StringIO.StringIO()
+        self.writeXML(output)
+        return output.getvalue()

Deleted: CalendarServer/trunk/twext/web2/dav/element/rfc2518.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/rfc2518.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/element/rfc2518.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,576 +0,0 @@
-##
-# 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
-##
-
-"""
-RFC 2518 (WebDAV) XML Elements
-
-This module provides XML element definitions for use with WebDAV.
-
-See RFC 2518: http://www.ietf.org/rfc/rfc2518.txt
-"""
-
-from twext.web2 import responsecode
-from twext.web2.dav.element.base import *
-from twext.web2.http_headers import MimeType
-
-##
-# Section 12
-##
-
-class ActiveLock (WebDAVElement):
-    """
-    Describes a lock on a resource. (RFC 2518, section 12.1)
-    """
-    name = "activelock"
-
-    allowed_children = {
-        (dav_namespace, "lockscope"): (1, 1),
-        (dav_namespace, "locktype" ): (1, 1),
-        (dav_namespace, "depth"    ): (1, 1),
-        (dav_namespace, "owner"    ): (0, 1),
-        (dav_namespace, "timeout"  ): (0, 1),
-        (dav_namespace, "locktoken"): (0, 1),
-    }
-
-class Depth (WebDAVTextElement):
-    """
-    The value of the depth header. (RFC 2518, section 12.1.1)
-    """
-    name = "depth"
-
-    def validate(self):
-        super(Depth, self).validate()
-
-        depth = str(self)
-        if depth not in ("0", "1", "infinity"):
-            raise ValueError("Invalid depth: %s" % (depth,))
-
-class LockToken (WebDAVElement):
-    """
-    The lock token associated with a lock. (RFC 2518, section 12.1.2)
-    """
-    name = "locktoken"
-
-    allowed_children = { (dav_namespace, "href"): (1, None) }
-
-class Timeout (WebDAVTextElement):
-    """
-    The timeout associated with a lock. (RFC 2518, section 12.1.3)
-    """
-    name = "timeout"
-
-class Collection (WebDAVEmptyElement):
-    """
-    Identifies the associated resource as a collection. (RFC 2518, section 12.2)
-    """
-    name = "collection"
-
-class HRef (WebDAVTextElement):
-    """
-    Identifies the content of the element as a URI. (RFC 2518, section 12.3)
-    """
-    name = "href"
-
-class Link (WebDAVElement):
-    """
-    Identifies the property as a link and contains the source and
-    destination of that link. (RFC 2518, section 12.4)
-    """
-    name = "link"
-
-    allowed_children = {
-        (dav_namespace, "src"): (1, None),
-        (dav_namespace, "dst"): (1, None),
-    }
-
-class LinkDestination (WebDAVTextElement):
-    """
-    Indicates the destination of a link. (RFC 2518, section 12.4.1)
-    """
-    name = "dst"
-
-class LinkSource (WebDAVTextElement):
-    """
-    Indicates the source of a link. (RFC 2518, section 12.4.2)
-    """
-    name = "src"
-
-class LockEntry (WebDAVElement):
-    """
-    Defines the types of lock that can be used with the
-    resource. (RFC 2518, section 12.5)
-    """
-    name = "lockentry"
-
-    allowed_children = {
-        (dav_namespace, "lockscope"): (1, 1),
-        (dav_namespace, "locktype" ): (1, 1),
-    }
-
-class LockInfo (WebDAVElement):
-    """
-    Used with a LOCK method to specify the type of lock that the
-    client wishes to have created. (RFC 2518, section 12.6)
-    """
-    name = "lockinfo"
-
-    allowed_children = {
-        (dav_namespace, "lockscope"): (1, 1),
-        (dav_namespace, "locktype" ): (1, 1),
-        (dav_namespace, "owner"    ): (0, 1),
-    }
-
-class LockScope (WebDAVOneShotElement):
-    """
-    Specifies whether a lock is an exclusive lock or a shared
-    lock. (RFC 2518, section 12.7)
-    """
-    name = "lockscope"
-
-    allowed_children = {
-        (dav_namespace, "exclusive"): (0, 1),
-        (dav_namespace, "shared"   ): (0, 1),
-    }
-
-class Exclusive (WebDAVEmptyElement):
-    """
-    Indicates an exclusive lock. (RFC 2518, section 12.7.1)
-    """
-    name = "exclusive"
-
-LockScope.exclusive = LockScope(Exclusive())
-
-class Shared (WebDAVEmptyElement):
-    """
-    Indicates a shared lock. (RFC 2518, section 12.7.2)
-    """
-    name = "shared"
-
-LockScope.shared = LockScope(Shared())
-
-class LockType (WebDAVOneShotElement):
-    """
-    Specifies the access type of a lock. (RFC 2518, section 12.8)
-    """
-    name = "locktype"
-
-    allowed_children = { (dav_namespace, "write"): (0, 1) }
-
-class Write (WebDAVEmptyElement):
-    """
-    Indicates a write lock. (RFC 2518, section 12.8.1)
-    Controls methods that lock a resource or modify the content, dead
-    properties, or (in the case of a collection) membership of a resource.
-    (RFC 3744, section 3.2)
-    """
-    name = "write"
-
-LockType.write = LockType(Write())
-
-class MultiStatus (WebDAVElement):
-    """
-    Contains multiple Responses. (RFC 2518, section 12.9)
-    """
-    name = "multistatus"
-
-    allowed_children = {
-        (dav_namespace, "response"           ): (0, None),
-        (dav_namespace, "responsedescription"): (0, 1),
-    }
-
-class Response (WebDAVElement):
-    """
-    Holds a single response describing the effect of a method on a
-    resource and/or its properties. (RFC 2518, section 12.9.1)
-    """
-    name = "response"
-
-    allowed_children = {
-        (dav_namespace, "href"               ): (1, None),
-        (dav_namespace, "status"             ): (1, 1),
-        (dav_namespace, "propstat"           ): (1, None),
-        (dav_namespace, "error"              ): (0, 1),        # 2518bis
-        (dav_namespace, "responsedescription"): (0, 1),
-    }
-
-    def __new__(clazz, *children):
-        if clazz is not Response: return WebDAVElement.__new__(clazz)
-
-        resource_count = 0
-        status_count   = 0
-        propstat_count = 0
-
-        for child in children:
-            if   isinstance(child, HRef          ): resource_count += 1
-            elif isinstance(child, Status        ): status_count   += 1
-            elif isinstance(child, PropertyStatus): propstat_count += 1
-
-        if resource_count < 1:
-            raise ValueError("%s element must have at least one %s."
-                             % (clazz.sname(), HRef.sname()))
-
-        if status_count is 0:
-            if propstat_count is 0:
-                raise ValueError("%s element must have one of %s or %s"
-                                 % (clazz.sname(), Status.sname(), PropertyStatus.sname()))
-
-            if resource_count > 1:
-                raise ValueError("%s element with %s may only have one %s"
-                                 % (clazz.sname(), PropertyStatus.sname(), HRef.sname()))
-
-            return PropertyStatusResponse.__new__(PropertyStatusResponse, *children)
-
-        if status_count > 1:
-            raise ValueError("%s element may only have one %s" % (clazz.sname(), Status.sname()))
-
-        return StatusResponse.__new__(StatusResponse, *children)
-
-class StatusResponse (Response):
-    """
-    Specialized derivative of Response for resource status.
-    """
-    unregistered = True
-
-    allowed_children = {
-        (dav_namespace, "href"               ): (1, None),
-        (dav_namespace, "status"             ): (1, 1),
-        (dav_namespace, "error"              ): (0, 1),        # 2518bis
-        (dav_namespace, "responsedescription"): (0, 1),
-    }
-
-class PropertyStatusResponse (Response):
-    """
-    Specialized derivative of Response for property status.
-    """
-    unregistered = True
-
-    allowed_children = {
-        (dav_namespace, "href"               ): (1, 1),
-        (dav_namespace, "propstat"           ): (1, None),
-        (dav_namespace, "error"              ): (0, 1),        # 2518bis
-        (dav_namespace, "responsedescription"): (0, 1),
-    }
-
-class PropertyStatus (WebDAVElement):
-    """
-    Groups together a Property and Status element that is associated
-    with a particular DAV:href element. (RFC 2518, section 12.9.1.1)
-    """
-    name = "propstat"
-
-    allowed_children = {
-        (dav_namespace, "prop"               ): (1, 1),
-        (dav_namespace, "status"             ): (1, 1),
-        (dav_namespace, "error"              ): (0, 1),        # 2518bis
-        (dav_namespace, "responsedescription"): (0, 1),
-    }
-
-class Status (WebDAVTextElement):
-    """
-    Holds a single HTTP status line. (RFC 2518, section 12.9.1.2)
-    """
-    name = "status"
-
-    def fromResponseCode(clazz, code):
-        """
-        code must be an integer response code in
-        twext.web2.responsecode.RESPONSES.keys()
-        """
-        if code not in responsecode.RESPONSES:
-            raise ValueError("Invalid response code: %r" % (code,))
-
-        return clazz(PCDATAElement("HTTP/1.1 %d %s" % (code, responsecode.RESPONSES[code])))
-
-    fromResponseCode = classmethod(fromResponseCode)
-
-    def __init__(self, *children, **attributes):
-        super(Status, self).__init__(*children, **attributes)
-
-        status = str(self)
-        if not status.startswith("HTTP/1.1 "):
-            raise ValueError("Invalid WebDAV status: %s" % (status,))
-
-        code = int(status[9:12])
-        if code not in responsecode.RESPONSES:
-            raise ValueError("Invalid status code: %s" % (code,))
-
-        self.code = code
-
-class ResponseDescription (WebDAVTextElement):
-    """
-    Contains a message that can be displayed to the user explaining the nature
-    of the response. (RFC 2518, section 12.9.2)
-    """
-    name = "responsedescription"
-
-class Owner (WebDAVElement):
-    """
-    Property which provides information about the principal taking out a lock.
-    (RFC 2518, section 12.10)
-    Property which identifies a principal as being the owner principal of a
-    resource. (RFC 3744, section 5.1)
-    Note that RFC 2518 allows any content, while RFC 3744 expect zero or one
-    DAV:href element.
-    """
-    name = "owner"
-    hidden = True
-    protected = True # may be protected, per RFC 3744, section 5.1
-
-    allowed_children = { WebDAVElement: (0, None) }
-
-class PropertyContainer (WebDAVElement):
-    """
-    Contains properties related to a resource. (RFC 2518, section 12.11)
-    """
-    name = "prop"
-
-    allowed_children = { WebDAVElement: (0, None) }
-
-class PropertyBehavior (WebDAVElement):
-    """
-    Specifies how properties are handled during a COPY or MOVE. (RFC 2518,
-    section 12.12)
-    """
-    name = "propertybehavior"
-
-    allowed_children = {
-        (dav_namespace, "omit"     ): (0, 1),
-        (dav_namespace, "keepalive"): (0, 1),
-    }
-
-    def __init__(self, *children, **attributes):
-        super(PropertyBehavior, self).__init__(*children, **attributes)
-
-        if len(self.children) != 1:
-            raise ValueError(
-                "Exactly one of DAV:omit, DAV:keepalive required for %s, got: %s"
-                % (self.sname(), self.children)
-            )
-
-        self.behavior = children[0]
-
-class KeepAlive (WebDAVElement):
-    """
-    Specifies requirements for the copying/moving or live properties. (RFC 2518,
-    section 12.12.1)
-    """
-    name = "keepalive"
-
-    allowed_children = {
-        (dav_namespace, "href"): (0, None),
-        PCDATAElement: (0, 1),
-    }
-
-    def validate(self):
-        super(KeepAlive, self).validate()
-
-        type = None
-
-        for child in self.children:
-            if type is None:
-                type = child.qname()
-            elif child.qname() != type:
-                raise ValueError(
-                    "Only one of DAV:href or PCDATA allowed for %s, got: %s"
-                    % (self.sname(), self.children)
-                )
-
-        if type == "#PCDATA":
-            if str(self) != "*":
-                raise ValueError("Invalid keepalive value: %r", (str(self),))
-
-class Omit (WebDAVEmptyElement):
-    """
-    Instructs the server that it should use best effort to copy properties. (RFC
-    2518, section 12.12.2)
-    """
-    name = "omit"
-
-class PropertyUpdate (WebDAVElement):
-    """
-    Contains a request to alter the properties on a resource. (RFC 2518, section
-    12.13)
-    """
-    name = "propertyupdate"
-
-    allowed_children = {
-        (dav_namespace, "remove"): (0, None),
-        (dav_namespace, "set"   ): (0, None),
-    }
-
-class Remove (WebDAVElement):
-    """
-    Lists the DAV properties to be removed from a resource. (RFC 2518, section
-    12.13.1)
-    """
-    name = "remove"
-
-    allowed_children = { (dav_namespace, "prop"): (1, 1) }
-
-class Set (WebDAVElement):
-    """
-    Lists the DAV properties to be set for a resource. (RFC 2518, section
-    12.13.2)
-    """
-    name = "set"
-
-    allowed_children = { (dav_namespace, "prop"): (1, 1) }
-
-class PropertyFind (WebDAVElement):
-    """
-    Specifies the properties to be returned from a PROPFIND
-    method. (RFC 2518, section 12.14)
-    """
-    name = "propfind"
-
-    allowed_children = {
-        (dav_namespace, "allprop" ): (0, 1),
-        (dav_namespace, "propname"): (0, 1),
-        (dav_namespace, "prop"    ): (0, 1),
-    }
-
-    def validate(self):
-        super(PropertyFind, self).validate()
-
-        if len(self.children) != 1:
-            raise ValueError(
-                "Exactly one of DAV:allprop, DAV:propname or DAV:prop is required for %s, got: %r"
-                % (self.sname(), self.children)
-            )
-
-class AllProperties (WebDAVEmptyElement):
-    """
-    Specifies that all property names and values on the resource are
-    to be returned. (RFC 2518, section 12.14.1)
-    """
-    name = "allprop"
-
-class PropertyName (WebDAVEmptyElement):
-    """
-    Specifies that only a list of property names on the resource are
-    to be returned. (RFC 2518, section 12.14.2)
-    """
-    name = "propname"
-
-##
-# Section 13
-##
-
-class CreationDate (WebDAVDateTimeElement):
-    """
-    Records the time and date that the resource was created. (RFC 2518, section
-    13.1)
-    """
-    name = "creationdate"
-    # MAY be protected as per RFC2518bis.  We may make this more flexible later.
-    protected = True
-
-class DisplayName (WebDAVTextElement):
-    """
-    Provides a name for the resource that is suitable for presentation
-    to a user. (RFC 2518, section 13.2)
-    """
-    name = "displayname"
-
-class GETContentLanguage (WebDAVTextElement):
-    """
-    Contains the Content-Language header returned by a GET without
-    accept headers. (RFC 2518, section 13.3)
-    """
-    name = "getcontentlanguage"
-
-class GETContentLength (WebDAVTextElement):
-    """
-    Contains the Content-Length header returned by a GET without
-    accept headers. (RFC 2518, section 13.4)
-    """
-    name = "getcontentlength"
-    protected = True
-
-class GETContentType (WebDAVTextElement):
-    """
-    Contains the Content-Type header returned by a GET without
-    accept headers. (RFC 2518, section 13.5)
-    """
-    name = "getcontenttype"
-
-    def mimeType(self):
-        return MimeType.fromString(str(self))
-
-class GETETag (WebDAVTextElement):
-    """
-    Contains the ETag header returned by a GET without
-    accept headers. (RFC 2518, section 13.6)
-    """
-    name = "getetag"
-    protected = True
-
-class GETLastModified (DateTimeHeaderElement):
-    """
-    Contains the Last-Modified header returned by a GET without accept
-    headers. (RFC 2518, section 13.7)
-    """
-    name = "getlastmodified"
-    protected = True
-
-class LockDiscovery (WebDAVElement):
-    """
-    Describes the active locks on a resource. (RFC 2518, section 13.8)
-    """
-    name = "lockdiscovery"
-    protected = True
-
-    allowed_children = { (dav_namespace, "activelock"): (0, None) }
-
-class ResourceType (WebDAVElement):
-    """
-    Specifies the nature of the resource. (RFC 2518, section 13.9)
-    """
-    name = "resourcetype"
-    protected = True
-
-    allowed_children = { WebDAVElement: (0, None) }
-
-ResourceType.collection = ResourceType(Collection())
-ResourceType.empty      = ResourceType()
-
-class Source (WebDAVElement):
-    """
-    The destination of the source link identifies the resource that
-    contains the unprocessed source of the link's source. (RFC 2518, section
-    13.10)
-    """
-    name = "source"
-
-    allowed_children = { (dav_namespace, "link"): (0, None) }
-
-class SupportedLock (WebDAVElement):
-    """
-    Provides a listing of the lock capabilities supported by the
-    resource. (RFC 2518, section 13.11)
-    """
-    name = "supportedlock"
-    protected = True
-
-    allowed_children = { (dav_namespace, "lockentry"): (0, None) }

Copied: CalendarServer/trunk/twext/web2/dav/element/rfc2518.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/rfc2518.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/element/rfc2518.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/element/rfc2518.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,576 @@
+##
+# 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
+##
+
+"""
+RFC 2518 (WebDAV) XML Elements
+
+This module provides XML element definitions for use with WebDAV.
+
+See RFC 2518: http://www.ietf.org/rfc/rfc2518.txt
+"""
+
+from twext.web2 import responsecode
+from twext.web2.dav.element.base import *
+from twext.web2.http_headers import MimeType
+
+##
+# Section 12
+##
+
+class ActiveLock (WebDAVElement):
+    """
+    Describes a lock on a resource. (RFC 2518, section 12.1)
+    """
+    name = "activelock"
+
+    allowed_children = {
+        (dav_namespace, "lockscope"): (1, 1),
+        (dav_namespace, "locktype" ): (1, 1),
+        (dav_namespace, "depth"    ): (1, 1),
+        (dav_namespace, "owner"    ): (0, 1),
+        (dav_namespace, "timeout"  ): (0, 1),
+        (dav_namespace, "locktoken"): (0, 1),
+    }
+
+class Depth (WebDAVTextElement):
+    """
+    The value of the depth header. (RFC 2518, section 12.1.1)
+    """
+    name = "depth"
+
+    def validate(self):
+        super(Depth, self).validate()
+
+        depth = str(self)
+        if depth not in ("0", "1", "infinity"):
+            raise ValueError("Invalid depth: %s" % (depth,))
+
+class LockToken (WebDAVElement):
+    """
+    The lock token associated with a lock. (RFC 2518, section 12.1.2)
+    """
+    name = "locktoken"
+
+    allowed_children = { (dav_namespace, "href"): (1, None) }
+
+class Timeout (WebDAVTextElement):
+    """
+    The timeout associated with a lock. (RFC 2518, section 12.1.3)
+    """
+    name = "timeout"
+
+class Collection (WebDAVEmptyElement):
+    """
+    Identifies the associated resource as a collection. (RFC 2518, section 12.2)
+    """
+    name = "collection"
+
+class HRef (WebDAVTextElement):
+    """
+    Identifies the content of the element as a URI. (RFC 2518, section 12.3)
+    """
+    name = "href"
+
+class Link (WebDAVElement):
+    """
+    Identifies the property as a link and contains the source and
+    destination of that link. (RFC 2518, section 12.4)
+    """
+    name = "link"
+
+    allowed_children = {
+        (dav_namespace, "src"): (1, None),
+        (dav_namespace, "dst"): (1, None),
+    }
+
+class LinkDestination (WebDAVTextElement):
+    """
+    Indicates the destination of a link. (RFC 2518, section 12.4.1)
+    """
+    name = "dst"
+
+class LinkSource (WebDAVTextElement):
+    """
+    Indicates the source of a link. (RFC 2518, section 12.4.2)
+    """
+    name = "src"
+
+class LockEntry (WebDAVElement):
+    """
+    Defines the types of lock that can be used with the
+    resource. (RFC 2518, section 12.5)
+    """
+    name = "lockentry"
+
+    allowed_children = {
+        (dav_namespace, "lockscope"): (1, 1),
+        (dav_namespace, "locktype" ): (1, 1),
+    }
+
+class LockInfo (WebDAVElement):
+    """
+    Used with a LOCK method to specify the type of lock that the
+    client wishes to have created. (RFC 2518, section 12.6)
+    """
+    name = "lockinfo"
+
+    allowed_children = {
+        (dav_namespace, "lockscope"): (1, 1),
+        (dav_namespace, "locktype" ): (1, 1),
+        (dav_namespace, "owner"    ): (0, 1),
+    }
+
+class LockScope (WebDAVOneShotElement):
+    """
+    Specifies whether a lock is an exclusive lock or a shared
+    lock. (RFC 2518, section 12.7)
+    """
+    name = "lockscope"
+
+    allowed_children = {
+        (dav_namespace, "exclusive"): (0, 1),
+        (dav_namespace, "shared"   ): (0, 1),
+    }
+
+class Exclusive (WebDAVEmptyElement):
+    """
+    Indicates an exclusive lock. (RFC 2518, section 12.7.1)
+    """
+    name = "exclusive"
+
+LockScope.exclusive = LockScope(Exclusive())
+
+class Shared (WebDAVEmptyElement):
+    """
+    Indicates a shared lock. (RFC 2518, section 12.7.2)
+    """
+    name = "shared"
+
+LockScope.shared = LockScope(Shared())
+
+class LockType (WebDAVOneShotElement):
+    """
+    Specifies the access type of a lock. (RFC 2518, section 12.8)
+    """
+    name = "locktype"
+
+    allowed_children = { (dav_namespace, "write"): (0, 1) }
+
+class Write (WebDAVEmptyElement):
+    """
+    Indicates a write lock. (RFC 2518, section 12.8.1)
+    Controls methods that lock a resource or modify the content, dead
+    properties, or (in the case of a collection) membership of a resource.
+    (RFC 3744, section 3.2)
+    """
+    name = "write"
+
+LockType.write = LockType(Write())
+
+class MultiStatus (WebDAVElement):
+    """
+    Contains multiple Responses. (RFC 2518, section 12.9)
+    """
+    name = "multistatus"
+
+    allowed_children = {
+        (dav_namespace, "response"           ): (0, None),
+        (dav_namespace, "responsedescription"): (0, 1),
+    }
+
+class Response (WebDAVElement):
+    """
+    Holds a single response describing the effect of a method on a
+    resource and/or its properties. (RFC 2518, section 12.9.1)
+    """
+    name = "response"
+
+    allowed_children = {
+        (dav_namespace, "href"               ): (1, None),
+        (dav_namespace, "status"             ): (1, 1),
+        (dav_namespace, "propstat"           ): (1, None),
+        (dav_namespace, "error"              ): (0, 1),        # 2518bis
+        (dav_namespace, "responsedescription"): (0, 1),
+    }
+
+    def __new__(clazz, *children):
+        if clazz is not Response: return WebDAVElement.__new__(clazz)
+
+        resource_count = 0
+        status_count   = 0
+        propstat_count = 0
+
+        for child in children:
+            if   isinstance(child, HRef          ): resource_count += 1
+            elif isinstance(child, Status        ): status_count   += 1
+            elif isinstance(child, PropertyStatus): propstat_count += 1
+
+        if resource_count < 1:
+            raise ValueError("%s element must have at least one %s."
+                             % (clazz.sname(), HRef.sname()))
+
+        if status_count is 0:
+            if propstat_count is 0:
+                raise ValueError("%s element must have one of %s or %s"
+                                 % (clazz.sname(), Status.sname(), PropertyStatus.sname()))
+
+            if resource_count > 1:
+                raise ValueError("%s element with %s may only have one %s"
+                                 % (clazz.sname(), PropertyStatus.sname(), HRef.sname()))
+
+            return PropertyStatusResponse.__new__(PropertyStatusResponse, *children)
+
+        if status_count > 1:
+            raise ValueError("%s element may only have one %s" % (clazz.sname(), Status.sname()))
+
+        return StatusResponse.__new__(StatusResponse, *children)
+
+class StatusResponse (Response):
+    """
+    Specialized derivative of Response for resource status.
+    """
+    unregistered = True
+
+    allowed_children = {
+        (dav_namespace, "href"               ): (1, None),
+        (dav_namespace, "status"             ): (1, 1),
+        (dav_namespace, "error"              ): (0, 1),        # 2518bis
+        (dav_namespace, "responsedescription"): (0, 1),
+    }
+
+class PropertyStatusResponse (Response):
+    """
+    Specialized derivative of Response for property status.
+    """
+    unregistered = True
+
+    allowed_children = {
+        (dav_namespace, "href"               ): (1, 1),
+        (dav_namespace, "propstat"           ): (1, None),
+        (dav_namespace, "error"              ): (0, 1),        # 2518bis
+        (dav_namespace, "responsedescription"): (0, 1),
+    }
+
+class PropertyStatus (WebDAVElement):
+    """
+    Groups together a Property and Status element that is associated
+    with a particular DAV:href element. (RFC 2518, section 12.9.1.1)
+    """
+    name = "propstat"
+
+    allowed_children = {
+        (dav_namespace, "prop"               ): (1, 1),
+        (dav_namespace, "status"             ): (1, 1),
+        (dav_namespace, "error"              ): (0, 1),        # 2518bis
+        (dav_namespace, "responsedescription"): (0, 1),
+    }
+
+class Status (WebDAVTextElement):
+    """
+    Holds a single HTTP status line. (RFC 2518, section 12.9.1.2)
+    """
+    name = "status"
+
+    def fromResponseCode(clazz, code):
+        """
+        code must be an integer response code in
+        twext.web2.responsecode.RESPONSES.keys()
+        """
+        if code not in responsecode.RESPONSES:
+            raise ValueError("Invalid response code: %r" % (code,))
+
+        return clazz(PCDATAElement("HTTP/1.1 %d %s" % (code, responsecode.RESPONSES[code])))
+
+    fromResponseCode = classmethod(fromResponseCode)
+
+    def __init__(self, *children, **attributes):
+        super(Status, self).__init__(*children, **attributes)
+
+        status = str(self)
+        if not status.startswith("HTTP/1.1 "):
+            raise ValueError("Invalid WebDAV status: %s" % (status,))
+
+        code = int(status[9:12])
+        if code not in responsecode.RESPONSES:
+            raise ValueError("Invalid status code: %s" % (code,))
+
+        self.code = code
+
+class ResponseDescription (WebDAVTextElement):
+    """
+    Contains a message that can be displayed to the user explaining the nature
+    of the response. (RFC 2518, section 12.9.2)
+    """
+    name = "responsedescription"
+
+class Owner (WebDAVElement):
+    """
+    Property which provides information about the principal taking out a lock.
+    (RFC 2518, section 12.10)
+    Property which identifies a principal as being the owner principal of a
+    resource. (RFC 3744, section 5.1)
+    Note that RFC 2518 allows any content, while RFC 3744 expect zero or one
+    DAV:href element.
+    """
+    name = "owner"
+    hidden = True
+    protected = True # may be protected, per RFC 3744, section 5.1
+
+    allowed_children = { WebDAVElement: (0, None) }
+
+class PropertyContainer (WebDAVElement):
+    """
+    Contains properties related to a resource. (RFC 2518, section 12.11)
+    """
+    name = "prop"
+
+    allowed_children = { WebDAVElement: (0, None) }
+
+class PropertyBehavior (WebDAVElement):
+    """
+    Specifies how properties are handled during a COPY or MOVE. (RFC 2518,
+    section 12.12)
+    """
+    name = "propertybehavior"
+
+    allowed_children = {
+        (dav_namespace, "omit"     ): (0, 1),
+        (dav_namespace, "keepalive"): (0, 1),
+    }
+
+    def __init__(self, *children, **attributes):
+        super(PropertyBehavior, self).__init__(*children, **attributes)
+
+        if len(self.children) != 1:
+            raise ValueError(
+                "Exactly one of DAV:omit, DAV:keepalive required for %s, got: %s"
+                % (self.sname(), self.children)
+            )
+
+        self.behavior = children[0]
+
+class KeepAlive (WebDAVElement):
+    """
+    Specifies requirements for the copying/moving or live properties. (RFC 2518,
+    section 12.12.1)
+    """
+    name = "keepalive"
+
+    allowed_children = {
+        (dav_namespace, "href"): (0, None),
+        PCDATAElement: (0, 1),
+    }
+
+    def validate(self):
+        super(KeepAlive, self).validate()
+
+        type = None
+
+        for child in self.children:
+            if type is None:
+                type = child.qname()
+            elif child.qname() != type:
+                raise ValueError(
+                    "Only one of DAV:href or PCDATA allowed for %s, got: %s"
+                    % (self.sname(), self.children)
+                )
+
+        if type == "#PCDATA":
+            if str(self) != "*":
+                raise ValueError("Invalid keepalive value: %r", (str(self),))
+
+class Omit (WebDAVEmptyElement):
+    """
+    Instructs the server that it should use best effort to copy properties. (RFC
+    2518, section 12.12.2)
+    """
+    name = "omit"
+
+class PropertyUpdate (WebDAVElement):
+    """
+    Contains a request to alter the properties on a resource. (RFC 2518, section
+    12.13)
+    """
+    name = "propertyupdate"
+
+    allowed_children = {
+        (dav_namespace, "remove"): (0, None),
+        (dav_namespace, "set"   ): (0, None),
+    }
+
+class Remove (WebDAVElement):
+    """
+    Lists the DAV properties to be removed from a resource. (RFC 2518, section
+    12.13.1)
+    """
+    name = "remove"
+
+    allowed_children = { (dav_namespace, "prop"): (1, 1) }
+
+class Set (WebDAVElement):
+    """
+    Lists the DAV properties to be set for a resource. (RFC 2518, section
+    12.13.2)
+    """
+    name = "set"
+
+    allowed_children = { (dav_namespace, "prop"): (1, 1) }
+
+class PropertyFind (WebDAVElement):
+    """
+    Specifies the properties to be returned from a PROPFIND
+    method. (RFC 2518, section 12.14)
+    """
+    name = "propfind"
+
+    allowed_children = {
+        (dav_namespace, "allprop" ): (0, 1),
+        (dav_namespace, "propname"): (0, 1),
+        (dav_namespace, "prop"    ): (0, 1),
+    }
+
+    def validate(self):
+        super(PropertyFind, self).validate()
+
+        if len(self.children) != 1:
+            raise ValueError(
+                "Exactly one of DAV:allprop, DAV:propname or DAV:prop is required for %s, got: %r"
+                % (self.sname(), self.children)
+            )
+
+class AllProperties (WebDAVEmptyElement):
+    """
+    Specifies that all property names and values on the resource are
+    to be returned. (RFC 2518, section 12.14.1)
+    """
+    name = "allprop"
+
+class PropertyName (WebDAVEmptyElement):
+    """
+    Specifies that only a list of property names on the resource are
+    to be returned. (RFC 2518, section 12.14.2)
+    """
+    name = "propname"
+
+##
+# Section 13
+##
+
+class CreationDate (WebDAVDateTimeElement):
+    """
+    Records the time and date that the resource was created. (RFC 2518, section
+    13.1)
+    """
+    name = "creationdate"
+    # MAY be protected as per RFC2518bis.  We may make this more flexible later.
+    protected = True
+
+class DisplayName (WebDAVTextElement):
+    """
+    Provides a name for the resource that is suitable for presentation
+    to a user. (RFC 2518, section 13.2)
+    """
+    name = "displayname"
+
+class GETContentLanguage (WebDAVTextElement):
+    """
+    Contains the Content-Language header returned by a GET without
+    accept headers. (RFC 2518, section 13.3)
+    """
+    name = "getcontentlanguage"
+
+class GETContentLength (WebDAVTextElement):
+    """
+    Contains the Content-Length header returned by a GET without
+    accept headers. (RFC 2518, section 13.4)
+    """
+    name = "getcontentlength"
+    protected = True
+
+class GETContentType (WebDAVTextElement):
+    """
+    Contains the Content-Type header returned by a GET without
+    accept headers. (RFC 2518, section 13.5)
+    """
+    name = "getcontenttype"
+
+    def mimeType(self):
+        return MimeType.fromString(str(self))
+
+class GETETag (WebDAVTextElement):
+    """
+    Contains the ETag header returned by a GET without
+    accept headers. (RFC 2518, section 13.6)
+    """
+    name = "getetag"
+    protected = True
+
+class GETLastModified (DateTimeHeaderElement):
+    """
+    Contains the Last-Modified header returned by a GET without accept
+    headers. (RFC 2518, section 13.7)
+    """
+    name = "getlastmodified"
+    protected = True
+
+class LockDiscovery (WebDAVElement):
+    """
+    Describes the active locks on a resource. (RFC 2518, section 13.8)
+    """
+    name = "lockdiscovery"
+    protected = True
+
+    allowed_children = { (dav_namespace, "activelock"): (0, None) }
+
+class ResourceType (WebDAVElement):
+    """
+    Specifies the nature of the resource. (RFC 2518, section 13.9)
+    """
+    name = "resourcetype"
+    protected = True
+
+    allowed_children = { WebDAVElement: (0, None) }
+
+ResourceType.collection = ResourceType(Collection())
+ResourceType.empty      = ResourceType()
+
+class Source (WebDAVElement):
+    """
+    The destination of the source link identifies the resource that
+    contains the unprocessed source of the link's source. (RFC 2518, section
+    13.10)
+    """
+    name = "source"
+
+    allowed_children = { (dav_namespace, "link"): (0, None) }
+
+class SupportedLock (WebDAVElement):
+    """
+    Provides a listing of the lock capabilities supported by the
+    resource. (RFC 2518, section 13.11)
+    """
+    name = "supportedlock"
+    protected = True
+
+    allowed_children = { (dav_namespace, "lockentry"): (0, None) }

Deleted: CalendarServer/trunk/twext/web2/dav/element/rfc3253.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/rfc3253.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/element/rfc3253.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,196 +0,0 @@
-##
-# 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
-##
-
-"""
-RFC 3253 (Versioning Extensions to WebDAV) XML Elements
-
-This module provides XML element definitions for use with WebDAV.
-
-See RFC 3253: http://www.ietf.org/rfc/rfc3253.txt
-"""
-
-from twext.web2.dav.element.base import *
-
-##
-# Section 1
-##
-
-class Error (WebDAVElement):
-    """
-    Specifies an error condition. (RFC 3253, section 1.6)
-    """
-    # FIXME: RFC 3253 doesn't quite seem to define this element...
-    # FIXME: Move when we update to RFC 2518bis
-    name = "error"
-
-    allowed_children = { WebDAVElement: (0, None) }
-
-##
-# Section 3
-##
-
-class Comment (WebDAVTextElement):
-    """
-    Property used to track a brief comment about a resource that is suitable for
-    presentation to a user. On a version, can be used to indicate why that
-    version was created. (RFC 3253, section 3.1.1)
-    """
-    name = "comment"
-    hidden = True
-
-class CreatorDisplayName (WebDAVTextElement):
-    """
-    Property which contains a description of the creator of the resource that is
-    suitable for presentation to a user. (RFC 3253, section 3.1.2)
-    """
-    name = "creator-displayname"
-    hidden = True
-
-class SupportedMethod (WebDAVElement):
-    """
-    Property which identifies a method that is supported by a resource. A method
-    is supported by a resource if there is some state of that resource for which
-    an application of that method will successfully satisfy all postconditions
-    of that method, including any additional postconditions added by the
-    features supported by that resource. (RFC 3253, section 3.1.3)
-    """
-    name = "supported-method"
-    hidden = True
-
-    allowed_children = { WebDAVElement: (0, None) }
-    allowed_attributes = { "name": True }
-
-class SupportedMethodSet (WebDAVElement):
-    """
-    Property which identifies the methods that are supported by a resource. (RFC
-    3253, section 3.1.3)
-    """
-    name = "supported-method-set"
-    protected = True
-    hidden = True
-
-    allowed_children = { (dav_namespace, "supported-method"): (0, None) }
-
-class SupportedLiveProperty (WebDAVElement):
-    """
-    Property which identifies a live property that is supported by a resource. A
-    live property is supported by a resource if that property has the semantics
-    defined for that property.  The value of this property must identify all
-    live properties defined by this document that are supported by the resource
-    and should identify all live properties that are supported by the resource.
-    (RFC 3253, section 3.1.4)
-    """
-    name = "supported-live-property"
-
-    # FIXME: Where is the name element defined?
-    allowed_children = { (dav_namespace, "name"): (1, 1) }
-
-class SupportedLivePropertySet (WebDAVElement):
-    """
-    Property which identifies the live properties that are supported by a
-    resource. (RFC 3253, section 3.1.4)
-    """
-    name = "supported-live-property-set"
-    hidden = True
-    protected = True
-
-    allowed_children = { (dav_namespace, "supported-live-property"): (0, None) }
-
-class Report (WebDAVElement):
-    """
-    A report. (RFC 3253, section 3.1.5)
-    """
-    # FIXME: Section 3.1.5 is pretty low on information.  Where else do we look?
-    name = "report"
-
-    allowed_children = { WebDAVElement: (0, None) }
-
-class SupportedReport (WebDAVElement):
-    """
-    Identifies a report that is supported by the resource.  (RFC 3253, section
-    3.1.5)
-    """
-    name = "supported-report"
-
-    #
-    # FIXME:
-    #
-    #   RFC 3253, section 3.1.5 defines supported-report as:
-    #
-    #     <!ELEMENT supported-report report>
-    #
-    #   Which means that a report child element is required.  However, section
-    # 3.6 defined a precondition with the same name (DAV:supported-report),
-    # which means that, according to section 1.6.1, this XML must be issued if
-    # the precondition fails:
-    #
-    #     <?xml version="1.0"?>
-    #     <D:error xmlns:D="DAV:">
-    #      <D:supported-report/>
-    #     </D:error>
-    #
-    #   Which is a problem because here we use supported-report with no
-    # children.
-    #
-    #   Absent any better guidance, we'll allow no children for this element for
-    # the time being.
-    #
-    allowed_children = { (dav_namespace, "report"): (0, 1) }
-
-class SupportedReportSet (WebDAVElement):
-    """
-    Property which identifies the reports that are supported by the resource.
-    (RFC 3253, section 3.1.5)
-    """
-    name = "supported-report-set"
-    hidden = True
-    protected = True
-
-    allowed_children = { (dav_namespace, "supported-report"): (0, None) }
-
-class ExpandProperty (WebDAVElement):
-    """
-    Report which provides a mechanism for retrieving in one request the
-    properties from resources identified by DAV:href property values.
-    (RFC 3253, section 3.8)
-    """
-    name = "expand-property"
-
-    allowed_children = { (dav_namespace, "property"): (0, None) }
-
-class Property (WebDAVElement):
-    """
-    Identifies a property by name. (RFC 3253, section 3.8)
-    Principal which matches a user if the value of the identified property of a
-    resource contains at most one DAV:href element, the value of that element
-    identifies a principal, and the user matches that principal. (RFC 3744,
-    section 5.5.1)
-    """
-    name = "property"
-
-    allowed_children = { (dav_namespace, "property"): (0, None) }
-    allowed_attributes = {
-        "name"      : True,
-        "namespace" : False,
-    }

Copied: CalendarServer/trunk/twext/web2/dav/element/rfc3253.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/rfc3253.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/element/rfc3253.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/element/rfc3253.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,196 @@
+##
+# 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
+##
+
+"""
+RFC 3253 (Versioning Extensions to WebDAV) XML Elements
+
+This module provides XML element definitions for use with WebDAV.
+
+See RFC 3253: http://www.ietf.org/rfc/rfc3253.txt
+"""
+
+from twext.web2.dav.element.base import *
+
+##
+# Section 1
+##
+
+class Error (WebDAVElement):
+    """
+    Specifies an error condition. (RFC 3253, section 1.6)
+    """
+    # FIXME: RFC 3253 doesn't quite seem to define this element...
+    # FIXME: Move when we update to RFC 2518bis
+    name = "error"
+
+    allowed_children = { WebDAVElement: (0, None) }
+
+##
+# Section 3
+##
+
+class Comment (WebDAVTextElement):
+    """
+    Property used to track a brief comment about a resource that is suitable for
+    presentation to a user. On a version, can be used to indicate why that
+    version was created. (RFC 3253, section 3.1.1)
+    """
+    name = "comment"
+    hidden = True
+
+class CreatorDisplayName (WebDAVTextElement):
+    """
+    Property which contains a description of the creator of the resource that is
+    suitable for presentation to a user. (RFC 3253, section 3.1.2)
+    """
+    name = "creator-displayname"
+    hidden = True
+
+class SupportedMethod (WebDAVElement):
+    """
+    Property which identifies a method that is supported by a resource. A method
+    is supported by a resource if there is some state of that resource for which
+    an application of that method will successfully satisfy all postconditions
+    of that method, including any additional postconditions added by the
+    features supported by that resource. (RFC 3253, section 3.1.3)
+    """
+    name = "supported-method"
+    hidden = True
+
+    allowed_children = { WebDAVElement: (0, None) }
+    allowed_attributes = { "name": True }
+
+class SupportedMethodSet (WebDAVElement):
+    """
+    Property which identifies the methods that are supported by a resource. (RFC
+    3253, section 3.1.3)
+    """
+    name = "supported-method-set"
+    protected = True
+    hidden = True
+
+    allowed_children = { (dav_namespace, "supported-method"): (0, None) }
+
+class SupportedLiveProperty (WebDAVElement):
+    """
+    Property which identifies a live property that is supported by a resource. A
+    live property is supported by a resource if that property has the semantics
+    defined for that property.  The value of this property must identify all
+    live properties defined by this document that are supported by the resource
+    and should identify all live properties that are supported by the resource.
+    (RFC 3253, section 3.1.4)
+    """
+    name = "supported-live-property"
+
+    # FIXME: Where is the name element defined?
+    allowed_children = { (dav_namespace, "name"): (1, 1) }
+
+class SupportedLivePropertySet (WebDAVElement):
+    """
+    Property which identifies the live properties that are supported by a
+    resource. (RFC 3253, section 3.1.4)
+    """
+    name = "supported-live-property-set"
+    hidden = True
+    protected = True
+
+    allowed_children = { (dav_namespace, "supported-live-property"): (0, None) }
+
+class Report (WebDAVElement):
+    """
+    A report. (RFC 3253, section 3.1.5)
+    """
+    # FIXME: Section 3.1.5 is pretty low on information.  Where else do we look?
+    name = "report"
+
+    allowed_children = { WebDAVElement: (0, None) }
+
+class SupportedReport (WebDAVElement):
+    """
+    Identifies a report that is supported by the resource.  (RFC 3253, section
+    3.1.5)
+    """
+    name = "supported-report"
+
+    #
+    # FIXME:
+    #
+    #   RFC 3253, section 3.1.5 defines supported-report as:
+    #
+    #     <!ELEMENT supported-report report>
+    #
+    #   Which means that a report child element is required.  However, section
+    # 3.6 defined a precondition with the same name (DAV:supported-report),
+    # which means that, according to section 1.6.1, this XML must be issued if
+    # the precondition fails:
+    #
+    #     <?xml version="1.0"?>
+    #     <D:error xmlns:D="DAV:">
+    #      <D:supported-report/>
+    #     </D:error>
+    #
+    #   Which is a problem because here we use supported-report with no
+    # children.
+    #
+    #   Absent any better guidance, we'll allow no children for this element for
+    # the time being.
+    #
+    allowed_children = { (dav_namespace, "report"): (0, 1) }
+
+class SupportedReportSet (WebDAVElement):
+    """
+    Property which identifies the reports that are supported by the resource.
+    (RFC 3253, section 3.1.5)
+    """
+    name = "supported-report-set"
+    hidden = True
+    protected = True
+
+    allowed_children = { (dav_namespace, "supported-report"): (0, None) }
+
+class ExpandProperty (WebDAVElement):
+    """
+    Report which provides a mechanism for retrieving in one request the
+    properties from resources identified by DAV:href property values.
+    (RFC 3253, section 3.8)
+    """
+    name = "expand-property"
+
+    allowed_children = { (dav_namespace, "property"): (0, None) }
+
+class Property (WebDAVElement):
+    """
+    Identifies a property by name. (RFC 3253, section 3.8)
+    Principal which matches a user if the value of the identified property of a
+    resource contains at most one DAV:href element, the value of that element
+    identifies a principal, and the user matches that principal. (RFC 3744,
+    section 5.5.1)
+    """
+    name = "property"
+
+    allowed_children = { (dav_namespace, "property"): (0, None) }
+    allowed_attributes = {
+        "name"      : True,
+        "namespace" : False,
+    }

Deleted: CalendarServer/trunk/twext/web2/dav/element/rfc3744.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/rfc3744.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/element/rfc3744.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,764 +0,0 @@
-##
-# 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
-##
-
-"""
-RFC 3744 (WebDAV Access Control Protocol) XML Elements
-
-This module provides XML element definitions for use with WebDAV.
-
-See RFC 3744: http://www.ietf.org/rfc/rfc3744.txt
-"""
-
-from twext.web2.dav.element.base import *
-
-##
-# Section 3 (Privileges)
-##
-
-class Read (WebDAVEmptyElement):
-    """
-    Privilege which controls methods that return information about the state
-    of a resource, including the resource's properties. (RFC 3744, section
-    3.1)
-    """
-    name = "read"
-
-# For DAV:write element (RFC 3744, section 3.2) see Write class above.
-
-class WriteProperties (WebDAVEmptyElement):
-    """
-    Privilege which controls methods that modify the dead properties of a
-    resource. (RFC 3744, section 3.3)
-    """
-    name = "write-properties"
-
-class WriteContent (WebDAVEmptyElement):
-    """
-    Privilege which controls methods that modify the content of an existing
-    resource. (RFC 3744, section 3.4)
-    """
-    name = "write-content"
-
-class Unlock (WebDAVEmptyElement):
-    """
-    Privilege which controls the use of the UNLOCK method by a principal other
-    than the lock owner. (RFC 3744, section 3.5)
-    """
-    name = "unlock"
-
-class ReadACL (WebDAVEmptyElement):
-    """
-    Privilege which controls the use of the PROPFIND method to retrieve the
-    DAV:acl property of a resource. (RFC 3744, section 3.6)
-    """
-    name = "read-acl"
-
-class ReadCurrentUserPrivilegeSet (WebDAVEmptyElement):
-    """
-    Privilege which controls the use of the PROPFIND method to retrieve the
-    DAV:current-user-privilege-set property of a resource. (RFC 3744, section
-    3.7)
-    """
-    name = "read-current-user-privilege-set"
-
-class WriteACL (WebDAVEmptyElement):
-    """
-    Privilege which controls the use of the ACL method to modify the DAV:acl
-    property of a resource. (RFC 3744, section 3.8)
-    """
-    name = "write-acl"
-
-class Bind (WebDAVEmptyElement):
-    """
-    Privilege which allows a method to add a new member URL from the a
-    collection resource. (RFC 3744, section 3.9)
-    """
-    name = "bind"
-
-class Unbind (WebDAVEmptyElement):
-    """
-    Privilege which allows a method to remove a member URL from the a collection
-    resource. (RFC 3744, section 3.10)
-    """
-    name = "unbind"
-
-class All (WebDAVEmptyElement):
-    """
-    Aggregate privilege that contains the entire set of privileges that can be
-    applied to a resource. (RFC 3744, section 3.11)
-    Principal which matches all users. (RFC 3744, section 5.5.1)
-    """
-    name = "all"
-
-##
-# Section 4 (Principal Properties)
-##
-
-class Principal (WebDAVElement):
-    """
-    Indicates a principal resource type. (RFC 3744, section 4)
-    Identifies the principal to which an ACE applies. (RFC 3744, section 5.5.1)
-    """
-    name = "principal"
-
-    allowed_children = {
-        (dav_namespace, "href"           ): (0, 1),
-        (dav_namespace, "all"            ): (0, 1),
-        (dav_namespace, "authenticated"  ): (0, 1),
-        (dav_namespace, "unauthenticated"): (0, 1),
-        (dav_namespace, "property"       ): (0, 1),
-        (dav_namespace, "self"           ): (0, 1),
-    }
-
-    def validate(self):
-        super(Principal, self).validate()
-
-        if len(self.children) > 1:
-            raise ValueError(
-                "Exactly one of DAV:href, DAV:all, DAV:authenticated, "
-                "DAV:unauthenticated, DAV:property or DAV:self is required for "
-                "%s, got: %r"
-                % (self.sname(), self.children)
-            )
-
-class AlternateURISet (WebDAVElement):
-    """
-    Property which contains the URIs of network resources with additional
-    descriptive information about the principal. (RFC 3744, section 4.1)
-    """
-    name = "alternate-URI-set"
-    hidden = True
-    protected = True
-
-    allowed_children = { (dav_namespace, "href"): (0, None) }
-
-class PrincipalURL (WebDAVElement):
-    """
-    Property which contains the URL that must be used to identify this principal
-    in an ACL request. (RFC 3744, section 4.2)
-    """
-    name = "principal-URL"
-    hidden = True
-    protected = True
-
-    allowed_children = { (dav_namespace, "href"): (0, 1) }
-
-class GroupMemberSet (WebDAVElement):
-    """
-    Property which identifies the principals that are direct members of a group
-    principal.
-    (RFC 3744, section 4.3)
-    """
-    name = "group-member-set"
-    hidden = True
-
-    allowed_children = { (dav_namespace, "href"): (0, None) }
-
-class GroupMembership (WebDAVElement):
-    """
-    Property which identifies the group principals in which a principal is
-    directly a member. (RFC 3744, section 4.4)
-    """
-    name = "group-membership"
-    hidden = True
-    protected = True
-
-    allowed_children = { (dav_namespace, "href"): (0, None) }
-
-##
-# Section 5 (Access Control Properties)
-##
-
-# For DAV:owner element (RFC 3744, section 5.1) see Owner class above.
-
-class Group (WebDAVElement):
-    """
-    Property which identifies a particular principal as being the group
-    principal of a resource. (RFC 3744, section 5.2)
-    """
-    name = "group"
-    hidden = True
-    protected = True # may be protected, per RFC 3744, section 5.2
-
-    allowed_children = { (dav_namespace, "href"): (0, 1) }
-
-class SupportedPrivilegeSet (WebDAVElement):
-    """
-    Property which identifies the privileges defined for a resource. (RFC 3744,
-    section 5.3)
-    """
-    name = "supported-privilege-set"
-    hidden = True
-    protected = True
-
-    allowed_children = { (dav_namespace, "supported-privilege"): (0, None) }
-
-class SupportedPrivilege (WebDAVElement):
-    """
-    Identifies a privilege defined for a resource. (RFC 3744, section 5.3)
-    """
-    name = "supported-privilege"
-
-    allowed_children = {
-        (dav_namespace, "privilege"          ): (1, 1),
-        (dav_namespace, "abstract"           ): (0, 1),
-        (dav_namespace, "description"        ): (1, 1),
-        (dav_namespace, "supported-privilege"): (0, None),
-    }
-
-class Privilege (WebDAVElement):
-    """
-    Identifies a privilege. (RFC 3744, sections 5.3 and 5.5.1)
-    """
-    name = "privilege"
-
-    allowed_children = { WebDAVElement: (0, None) }
-
-    def isAggregateOf(self, subprivilege, supportedPrivileges):
-        """
-        Check whether this privilege is an aggregate of another.
-        @param subprivilege: a L{Privilege}
-        @param supportedPrivileges: a L{SupportedPrivilegeSet}
-        @return: C{True} is this privilege is an aggregate of C{subprivilege}
-            according to C{supportedPrivileges}.
-        """
-        # DAV: all is an aggregate of all privileges
-        if len(self.children) == 1 and self.children[0].qname() == (dav_namespace, "all"):
-            return True
-
-        def isAggregate(supportedPrivilege):
-            sp = supportedPrivilege.childOfType(Privilege)
-
-            if sp == self:
-                def find(supportedPrivilege):
-                    if supportedPrivilege.childOfType(Privilege) == subprivilege:
-                        return True
-
-                    for child in supportedPrivilege.childrenOfType(SupportedPrivilege):
-                        if find(child):
-                            return True
-                    else:
-                        return False
-
-                return find(supportedPrivilege)
-            else:
-                for child in supportedPrivilege.childrenOfType(SupportedPrivilege):
-                    if isAggregate(child):
-                        return True
-                else:
-                    return False
-
-        for supportedPrivilege in supportedPrivileges.children:
-            if isAggregate(supportedPrivilege):
-                return True
-        else:
-            return False
-
-    def expandAggregate(self, supportedPrivileges):
-        """
-        Expand this privilege into the set of privileges aggregated under it
-        based on the structure of the given supported privileges. If this
-        privilege is not an aggregate, just return it as-is.
-        @param supportedPrivileges: a L{SupportedPrivilegeSet}
-        @return: the list of expanded L{Privileges}
-        """
-
-        # Find ourselves in supported privileges
-        def find(supportedPrivilege):
-            """
-            Find the supportPrivilege which matches this privilege.
-            """
-            if supportedPrivilege.childOfType(Privilege) == self:
-                return supportedPrivilege
-
-            for child in supportedPrivilege.childrenOfType(SupportedPrivilege):
-                result = find(child)
-                if result is not None:
-                    return result
-            else:
-                return None
-
-        for supportedPrivilege in supportedPrivileges.children:
-            result = find(supportedPrivilege)
-            if result is not None:
-                break
-        else:
-            return [self]
-
-        # Now add sub-privileges recursively
-        aggregates = []
-        def add(supportedPrivilege):
-            """
-            Add all sub-privileges to the list.
-            """
-            aggregates.append(supportedPrivilege.childOfType(Privilege))
-            for child in supportedPrivilege.childrenOfType(SupportedPrivilege):
-                add(child)
-        add(result)
-
-        return aggregates
-
-class Abstract (WebDAVElement):
-    """
-    Identifies a privilege as abstract. (RFC 3744, section 5.3)
-    """
-    name = "abstract"
-
-class Description (WebDAVTextElement):
-    """
-    A human-readable description of what privilege controls access to. (RFC
-    3744, sections 5.3 and 9.5)
-    """
-    name = "description"
-    allowed_attributes = { "xml:lang": True }
-
-class CurrentUserPrivilegeSet (WebDAVElement):
-    """
-    Property which contains the exact set of privileges (as computer by the
-    server) granted to the currently authenticated HTTP user. (RFC 3744, section
-    5.4)
-    """
-    name = "current-user-privilege-set"
-    hidden = True
-    protected = True
-
-    allowed_children = { (dav_namespace, "privilege"): (0, None) }
-
-# For DAV:privilege element (RFC 3744, section 5.4) see Privilege class above.
-
-class ACL (WebDAVElement):
-    """
-    Property which specifies the list of access control entries which define
-    what privileges are granted to which users for a resource. (RFC 3744,
-    section 5.5)
-    """
-    name = "acl"
-    hidden = True
-    protected = True
-
-    allowed_children = { (dav_namespace, "ace"): (0, None) }
-
-class ACE (WebDAVElement):
-    """
-    Specifies the list of access control entries which define what privileges
-    are granted to which users for a resource. (RFC 3744, section 5.5)
-    """
-    name = "ace"
-
-    allowed_children = {
-        (dav_namespace, "principal"): (0, 1),
-        (dav_namespace, "invert"   ): (0, 1),
-        (dav_namespace, "grant"    ): (0, 1),
-        (dav_namespace, "deny"     ): (0, 1),
-        (dav_namespace, "protected"): (0, 1),
-        (dav_namespace, "inherited"): (0, 1),
-    }
-
-    def __init__(self, *children, **attributes):
-        super(ACE, self).__init__(*children, **attributes)
-
-        self.principal  = None
-        self.invert     = None
-        self.allow      = None
-        self.privileges = None
-        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:
-                        raise ValueError(
-                            "Only one of DAV:principal or DAV:invert allowed in %s, got: %s"
-                            % (self.sname(), self.children)
-                        )
-                    if name == "invert":
-                        self.invert    = True
-                        self.principal = child.children[0]
-                    else:
-                        self.invert    = False
-                        self.principal = child
-    
-                elif name in ("grant", "deny"):
-                    if self.allow is not None:
-                        raise ValueError(
-                            "Only one of DAV:grant or DAV:deny allowed in %s, got: %s"
-                            % (self.sname(), self.children)
-                        )
-                    self.allow      = (name == "grant")
-                    self.privileges = child.children
-    
-                elif name == "inherited":
-                    self.inherited = str(child.children[0])
-    
-                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"
-                % (self.sname(), self.children)
-            )
-        assert self.invert is not None
-
-        if self.allow is None:
-            raise ValueError(
-                "One of DAV:grant or DAV:deny is required in %s, got: %s"
-                % (self.sname(), self.children)
-            )
-        assert self.privileges is not None
-
-# For DAV:principal element (RFC 3744, section 5.5.1) see Principal class above.
-
-# For DAV:all element (RFC 3744, section 5.5.1) see All class above.
-
-class Authenticated (WebDAVEmptyElement):
-    """
-    Principal which matches authenticated users. (RFC 3744, section 5.5.1)
-    """
-    name = "authenticated"
-
-class Unauthenticated (WebDAVEmptyElement):
-    """
-    Principal which matches unauthenticated users. (RFC 3744, section 5.5.1)
-    """
-    name = "unauthenticated"
-
-# For DAV:property element (RFC 3744, section 5.5.1) see Property class above.
-
-class Self (WebDAVEmptyElement):
-    """
-    Principal which matches a user if a resource is a principal and the user
-    matches the resource. (RFC 3744, sections 5.5.1 and 9.3)
-    """
-    name = "self"
-
-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)
-    """
-    name = "invert"
-
-    allowed_children = { (dav_namespace, "principal"): (1, 1) }
-
-class Grant (WebDAVElement):
-    """
-    Grants the contained privileges to a principal. (RFC 3744, section 5.5.2)
-    """
-    name = "grant"
-
-    allowed_children = { (dav_namespace, "privilege"): (1, None) }
-
-class Deny (WebDAVElement):
-    """
-    Denies the contained privileges to a principal. (RFC 3744, section 5.5.2)
-    """
-    name = "deny"
-
-    allowed_children = { (dav_namespace, "privilege"): (1, None) }
-
-# For DAV:privilege element (RFC 3744, section 5.5.2) see Privilege class above.
-
-class Protected (WebDAVEmptyElement):
-    """
-    Identifies an ACE as protected. (RFC 3744, section 5.5.3)
-    """
-    name = "protected"
-
-class Inherited (WebDAVElement):
-    """
-    Indicates that an ACE is inherited from the resource indentified by the
-    contained DAV:href element. (RFC 3744, section 5.5.4)
-    """
-    name = "inherited"
-
-    allowed_children = { (dav_namespace, "href"): (1, 1) }
-
-class ACLRestrictions (WebDAVElement):
-    """
-    Property which defines the types of ACLs supported by this server, to avoid
-    clients needlessly getting errors. (RFC 3744, section 5.6)
-    """
-    name = "acl-restrictions"
-    hidden = True
-    protected = True
-
-    allowed_children = {
-        (dav_namespace, "grant-only"        ): (0, 1),
-        (dav_namespace, "no-invert"         ): (0, 1),
-        (dav_namespace, "deny-before-grant" ): (0, 1),
-        (dav_namespace, "required-principal"): (0, 1),
-    }
-
-class GrantOnly (WebDAVEmptyElement):
-    """
-    Indicates that ACEs with deny clauses are not allowed. (RFC 3744, section
-    5.6.1)
-    """
-    name = "grant-only"
-
-class NoInvert (WebDAVEmptyElement):
-    """
-    Indicates that ACEs with the DAV:invert element are not allowed. (RFC 3744,
-    section 5.6.2)
-    """
-    name = "no-invert"
-
-class DenyBeforeGrant (WebDAVEmptyElement):
-    """
-    Indicates that all deny ACEs must precede all grant ACEs. (RFC 3744, section
-    5.6.3)
-    """
-    name = "deny-before-grant"
-
-class RequiredPrincipal (WebDAVElement):
-    """
-    Indicates which principals must have an ACE defined in an ACL. (RFC 3744,
-    section 5.6.4)
-    """
-    name = "required-principal"
-
-    allowed_children = {
-        (dav_namespace, "all"            ): (0, 1),
-        (dav_namespace, "authenticated"  ): (0, 1),
-        (dav_namespace, "unauthenticated"): (0, 1),
-        (dav_namespace, "self"           ): (0, 1),
-        (dav_namespace, "href"           ): (0, None),
-        (dav_namespace, "property"       ): (0, None),
-    }
-
-    def validate(self):
-        super(RequiredPrincipal, self).validate()
-
-        type = None
-
-        for child in self.children:
-            if type is None:
-                type = child.qname()
-            elif child.qname() != type:
-                raise ValueError(
-                    "Only one of DAV:all, DAV:authenticated, DAV:unauthenticated, "
-                    "DAV:self, DAV:href or DAV:property allowed for %s, got: %s"
-                    % (self.sname(), self.children)
-                )
-
-class InheritedACLSet (WebDAVElement):
-    """
-    Property which contains a set of URLs that identify other resources that
-    also control the access to this resource. (RFC 3744, section 5.7)
-    """
-    name = "inherited-acl-set"
-    hidden = True
-    protected = True
-
-    allowed_children = { (dav_namespace, "href"): (0, None) }
-
-class PrincipalCollectionSet (WebDAVElement):
-    """
-    Property which contains a set of URLs that identify the root collections
-    that contain the principals that are available on the server that implements
-    a resource. (RFC 3744, section 5.8)
-    """
-    name = "principal-collection-set"
-    hidden = True
-    protected = True
-
-    allowed_children = { (dav_namespace, "href"): (0, None) }
-
-##
-# Section 7 (Access Control and existing methods)
-##
-
-class NeedPrivileges (WebDAVElement):
-    """
-    Error which indicates insufficient privileges. (RFC 3744, section 7.1.1)
-    """
-    name = "need-privileges"
-
-    allowed_children = { (dav_namespace, "resource"): (0, None) }
-
-class Resource (WebDAVElement):
-    """
-    Identifies which resource had insufficient privileges. (RFC 3744, section
-    7.1.1)
-    """
-    name = "resource"
-
-    allowed_children = {
-        (dav_namespace, "href"     ): (1, 1),
-        (dav_namespace, "privilege"): (1, 1),
-    }
-
-##
-# Section 9 (Access Control Reports)
-##
-
-class ACLPrincipalPropSet (WebDAVElement):
-    """
-    Report which returns, for all principals in the DAV:acl property (of the
-    resource identified by the Request-URI) that are identified by http(s) URLs
-    or by a DAV:property principal, the value of the properties specified in the
-    REPORT request body. (RFC 3744, section 9.2)
-    """
-    name = "acl-principal-prop-set"
-
-    allowed_children = { WebDAVElement: (0, None) }
-
-    def validate(self):
-        super(ACLPrincipalPropSet, self).validate()
-
-        prop = False
-        
-        for child in self.children:
-            if child.qname() == (dav_namespace, "prop"):
-                if prop:
-                    raise ValueError(
-                        "Only one DAV:prop allowed for %s, got: %s"
-                        % (self.sname(), self.children)
-                    )
-                prop = True
-
-class PrincipalMatch (WebDAVElement):
-    """
-    Report used to identify all members (at any depth) of the collection
-    identified by the Request-URI that are principals and that match the current
-    user. (RFC 3744, section 9.3)
-    """
-    name = "principal-match"
-
-    allowed_children = {
-        (dav_namespace, "principal-property"): (0, 1),
-        (dav_namespace, "self"              ): (0, 1),
-        (dav_namespace, "prop"              ): (0, 1),
-    }
-
-    def validate(self):
-        super(PrincipalMatch, self).validate()
-
-        # This element can be empty when uses in supported-report-set
-        if not len(self.children):
-            return
-
-        principalPropertyOrSelf = False
-
-        for child in self.children:
-            namespace, name = child.qname()
-
-            if (namespace == dav_namespace) and name in ("principal-property", "self"):
-                if principalPropertyOrSelf:
-                    raise ValueError(
-                        "Only one of DAV:principal-property or DAV:self allowed in %s, got: %s"
-                        % (self.sname(), self.children)
-                    )
-                principalPropertyOrSelf = True
-
-        if not principalPropertyOrSelf:
-            raise ValueError(
-                "One of DAV:principal-property or DAV:self is required in %s, got: %s"
-                % (self.sname(), self.children)
-            )
-
-class PrincipalProperty (WebDAVElement):
-    """
-    Identifies a property. (RFC 3744, section 9.3)
-    """
-    name = "principal-property"
-
-    allowed_children = { WebDAVElement: (0, None) }
-
-# For DAV:self element (RFC 3744, section 9.3) see Self class above.
-
-class PrincipalPropertySearch (WebDAVElement):
-    """
-    Report which performs a search for all principals whose properties contain
-    character data that matches the search criteria specified in the request.
-    (RFC 3744, section 9.4)
-    """
-    name = "principal-property-search"
-
-    allowed_children = {
-        (dav_namespace, "property-search"                  ): (0, None),    # This is required but this element must be empty in supported-report-set
-        (dav_namespace, "prop"                             ): (0, 1),
-        (dav_namespace, "apply-to-principal-collection-set"): (0, 1),
-    }
-    allowed_attributes = { "test": False }
-
-class PropertySearch (WebDAVElement):
-    """
-    Contains a DAV:prop element enumerating the properties to be searched and a
-    DAV:match element, containing the search string. (RFC 3744, section 9.4)
-    """
-    name = "property-search"
-
-    allowed_children = {
-        (dav_namespace, "prop" ): (1, 1),
-        (dav_namespace, "match"): (1, 1),
-    }
-
-class Match (WebDAVTextElement):
-    """
-    Contains a search string. (RFC 3744, section 9.4)
-    """
-    name = "match"
-
-class PrincipalSearchPropertySet (WebDAVElement):
-    """
-    Report which identifies those properties that may be searched using the
-    DAV:principal-property-search report. (RFC 3744, section 9.5)
-    """
-    name = "principal-search-property-set"
-
-    allowed_children = { (dav_namespace, "principal-search-property"): (0, None) }
-
-class PrincipalSearchProperty (WebDAVElement):
-    """
-    Contains exactly one searchable property, and a description of the property.
-    (RFC 3744, section 9.5)
-    """
-    name = "principal-search-property"
-
-    allowed_children = {
-        (dav_namespace, "prop"       ): (1, 1),
-        (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/twext/web2/dav/element/rfc3744.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/rfc3744.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/element/rfc3744.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/element/rfc3744.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,764 @@
+##
+# 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
+##
+
+"""
+RFC 3744 (WebDAV Access Control Protocol) XML Elements
+
+This module provides XML element definitions for use with WebDAV.
+
+See RFC 3744: http://www.ietf.org/rfc/rfc3744.txt
+"""
+
+from twext.web2.dav.element.base import *
+
+##
+# Section 3 (Privileges)
+##
+
+class Read (WebDAVEmptyElement):
+    """
+    Privilege which controls methods that return information about the state
+    of a resource, including the resource's properties. (RFC 3744, section
+    3.1)
+    """
+    name = "read"
+
+# For DAV:write element (RFC 3744, section 3.2) see Write class above.
+
+class WriteProperties (WebDAVEmptyElement):
+    """
+    Privilege which controls methods that modify the dead properties of a
+    resource. (RFC 3744, section 3.3)
+    """
+    name = "write-properties"
+
+class WriteContent (WebDAVEmptyElement):
+    """
+    Privilege which controls methods that modify the content of an existing
+    resource. (RFC 3744, section 3.4)
+    """
+    name = "write-content"
+
+class Unlock (WebDAVEmptyElement):
+    """
+    Privilege which controls the use of the UNLOCK method by a principal other
+    than the lock owner. (RFC 3744, section 3.5)
+    """
+    name = "unlock"
+
+class ReadACL (WebDAVEmptyElement):
+    """
+    Privilege which controls the use of the PROPFIND method to retrieve the
+    DAV:acl property of a resource. (RFC 3744, section 3.6)
+    """
+    name = "read-acl"
+
+class ReadCurrentUserPrivilegeSet (WebDAVEmptyElement):
+    """
+    Privilege which controls the use of the PROPFIND method to retrieve the
+    DAV:current-user-privilege-set property of a resource. (RFC 3744, section
+    3.7)
+    """
+    name = "read-current-user-privilege-set"
+
+class WriteACL (WebDAVEmptyElement):
+    """
+    Privilege which controls the use of the ACL method to modify the DAV:acl
+    property of a resource. (RFC 3744, section 3.8)
+    """
+    name = "write-acl"
+
+class Bind (WebDAVEmptyElement):
+    """
+    Privilege which allows a method to add a new member URL from the a
+    collection resource. (RFC 3744, section 3.9)
+    """
+    name = "bind"
+
+class Unbind (WebDAVEmptyElement):
+    """
+    Privilege which allows a method to remove a member URL from the a collection
+    resource. (RFC 3744, section 3.10)
+    """
+    name = "unbind"
+
+class All (WebDAVEmptyElement):
+    """
+    Aggregate privilege that contains the entire set of privileges that can be
+    applied to a resource. (RFC 3744, section 3.11)
+    Principal which matches all users. (RFC 3744, section 5.5.1)
+    """
+    name = "all"
+
+##
+# Section 4 (Principal Properties)
+##
+
+class Principal (WebDAVElement):
+    """
+    Indicates a principal resource type. (RFC 3744, section 4)
+    Identifies the principal to which an ACE applies. (RFC 3744, section 5.5.1)
+    """
+    name = "principal"
+
+    allowed_children = {
+        (dav_namespace, "href"           ): (0, 1),
+        (dav_namespace, "all"            ): (0, 1),
+        (dav_namespace, "authenticated"  ): (0, 1),
+        (dav_namespace, "unauthenticated"): (0, 1),
+        (dav_namespace, "property"       ): (0, 1),
+        (dav_namespace, "self"           ): (0, 1),
+    }
+
+    def validate(self):
+        super(Principal, self).validate()
+
+        if len(self.children) > 1:
+            raise ValueError(
+                "Exactly one of DAV:href, DAV:all, DAV:authenticated, "
+                "DAV:unauthenticated, DAV:property or DAV:self is required for "
+                "%s, got: %r"
+                % (self.sname(), self.children)
+            )
+
+class AlternateURISet (WebDAVElement):
+    """
+    Property which contains the URIs of network resources with additional
+    descriptive information about the principal. (RFC 3744, section 4.1)
+    """
+    name = "alternate-URI-set"
+    hidden = True
+    protected = True
+
+    allowed_children = { (dav_namespace, "href"): (0, None) }
+
+class PrincipalURL (WebDAVElement):
+    """
+    Property which contains the URL that must be used to identify this principal
+    in an ACL request. (RFC 3744, section 4.2)
+    """
+    name = "principal-URL"
+    hidden = True
+    protected = True
+
+    allowed_children = { (dav_namespace, "href"): (0, 1) }
+
+class GroupMemberSet (WebDAVElement):
+    """
+    Property which identifies the principals that are direct members of a group
+    principal.
+    (RFC 3744, section 4.3)
+    """
+    name = "group-member-set"
+    hidden = True
+
+    allowed_children = { (dav_namespace, "href"): (0, None) }
+
+class GroupMembership (WebDAVElement):
+    """
+    Property which identifies the group principals in which a principal is
+    directly a member. (RFC 3744, section 4.4)
+    """
+    name = "group-membership"
+    hidden = True
+    protected = True
+
+    allowed_children = { (dav_namespace, "href"): (0, None) }
+
+##
+# Section 5 (Access Control Properties)
+##
+
+# For DAV:owner element (RFC 3744, section 5.1) see Owner class above.
+
+class Group (WebDAVElement):
+    """
+    Property which identifies a particular principal as being the group
+    principal of a resource. (RFC 3744, section 5.2)
+    """
+    name = "group"
+    hidden = True
+    protected = True # may be protected, per RFC 3744, section 5.2
+
+    allowed_children = { (dav_namespace, "href"): (0, 1) }
+
+class SupportedPrivilegeSet (WebDAVElement):
+    """
+    Property which identifies the privileges defined for a resource. (RFC 3744,
+    section 5.3)
+    """
+    name = "supported-privilege-set"
+    hidden = True
+    protected = True
+
+    allowed_children = { (dav_namespace, "supported-privilege"): (0, None) }
+
+class SupportedPrivilege (WebDAVElement):
+    """
+    Identifies a privilege defined for a resource. (RFC 3744, section 5.3)
+    """
+    name = "supported-privilege"
+
+    allowed_children = {
+        (dav_namespace, "privilege"          ): (1, 1),
+        (dav_namespace, "abstract"           ): (0, 1),
+        (dav_namespace, "description"        ): (1, 1),
+        (dav_namespace, "supported-privilege"): (0, None),
+    }
+
+class Privilege (WebDAVElement):
+    """
+    Identifies a privilege. (RFC 3744, sections 5.3 and 5.5.1)
+    """
+    name = "privilege"
+
+    allowed_children = { WebDAVElement: (0, None) }
+
+    def isAggregateOf(self, subprivilege, supportedPrivileges):
+        """
+        Check whether this privilege is an aggregate of another.
+        @param subprivilege: a L{Privilege}
+        @param supportedPrivileges: a L{SupportedPrivilegeSet}
+        @return: C{True} is this privilege is an aggregate of C{subprivilege}
+            according to C{supportedPrivileges}.
+        """
+        # DAV: all is an aggregate of all privileges
+        if len(self.children) == 1 and self.children[0].qname() == (dav_namespace, "all"):
+            return True
+
+        def isAggregate(supportedPrivilege):
+            sp = supportedPrivilege.childOfType(Privilege)
+
+            if sp == self:
+                def find(supportedPrivilege):
+                    if supportedPrivilege.childOfType(Privilege) == subprivilege:
+                        return True
+
+                    for child in supportedPrivilege.childrenOfType(SupportedPrivilege):
+                        if find(child):
+                            return True
+                    else:
+                        return False
+
+                return find(supportedPrivilege)
+            else:
+                for child in supportedPrivilege.childrenOfType(SupportedPrivilege):
+                    if isAggregate(child):
+                        return True
+                else:
+                    return False
+
+        for supportedPrivilege in supportedPrivileges.children:
+            if isAggregate(supportedPrivilege):
+                return True
+        else:
+            return False
+
+    def expandAggregate(self, supportedPrivileges):
+        """
+        Expand this privilege into the set of privileges aggregated under it
+        based on the structure of the given supported privileges. If this
+        privilege is not an aggregate, just return it as-is.
+        @param supportedPrivileges: a L{SupportedPrivilegeSet}
+        @return: the list of expanded L{Privileges}
+        """
+
+        # Find ourselves in supported privileges
+        def find(supportedPrivilege):
+            """
+            Find the supportPrivilege which matches this privilege.
+            """
+            if supportedPrivilege.childOfType(Privilege) == self:
+                return supportedPrivilege
+
+            for child in supportedPrivilege.childrenOfType(SupportedPrivilege):
+                result = find(child)
+                if result is not None:
+                    return result
+            else:
+                return None
+
+        for supportedPrivilege in supportedPrivileges.children:
+            result = find(supportedPrivilege)
+            if result is not None:
+                break
+        else:
+            return [self]
+
+        # Now add sub-privileges recursively
+        aggregates = []
+        def add(supportedPrivilege):
+            """
+            Add all sub-privileges to the list.
+            """
+            aggregates.append(supportedPrivilege.childOfType(Privilege))
+            for child in supportedPrivilege.childrenOfType(SupportedPrivilege):
+                add(child)
+        add(result)
+
+        return aggregates
+
+class Abstract (WebDAVElement):
+    """
+    Identifies a privilege as abstract. (RFC 3744, section 5.3)
+    """
+    name = "abstract"
+
+class Description (WebDAVTextElement):
+    """
+    A human-readable description of what privilege controls access to. (RFC
+    3744, sections 5.3 and 9.5)
+    """
+    name = "description"
+    allowed_attributes = { "xml:lang": True }
+
+class CurrentUserPrivilegeSet (WebDAVElement):
+    """
+    Property which contains the exact set of privileges (as computer by the
+    server) granted to the currently authenticated HTTP user. (RFC 3744, section
+    5.4)
+    """
+    name = "current-user-privilege-set"
+    hidden = True
+    protected = True
+
+    allowed_children = { (dav_namespace, "privilege"): (0, None) }
+
+# For DAV:privilege element (RFC 3744, section 5.4) see Privilege class above.
+
+class ACL (WebDAVElement):
+    """
+    Property which specifies the list of access control entries which define
+    what privileges are granted to which users for a resource. (RFC 3744,
+    section 5.5)
+    """
+    name = "acl"
+    hidden = True
+    protected = True
+
+    allowed_children = { (dav_namespace, "ace"): (0, None) }
+
+class ACE (WebDAVElement):
+    """
+    Specifies the list of access control entries which define what privileges
+    are granted to which users for a resource. (RFC 3744, section 5.5)
+    """
+    name = "ace"
+
+    allowed_children = {
+        (dav_namespace, "principal"): (0, 1),
+        (dav_namespace, "invert"   ): (0, 1),
+        (dav_namespace, "grant"    ): (0, 1),
+        (dav_namespace, "deny"     ): (0, 1),
+        (dav_namespace, "protected"): (0, 1),
+        (dav_namespace, "inherited"): (0, 1),
+    }
+
+    def __init__(self, *children, **attributes):
+        super(ACE, self).__init__(*children, **attributes)
+
+        self.principal  = None
+        self.invert     = None
+        self.allow      = None
+        self.privileges = None
+        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:
+                        raise ValueError(
+                            "Only one of DAV:principal or DAV:invert allowed in %s, got: %s"
+                            % (self.sname(), self.children)
+                        )
+                    if name == "invert":
+                        self.invert    = True
+                        self.principal = child.children[0]
+                    else:
+                        self.invert    = False
+                        self.principal = child
+    
+                elif name in ("grant", "deny"):
+                    if self.allow is not None:
+                        raise ValueError(
+                            "Only one of DAV:grant or DAV:deny allowed in %s, got: %s"
+                            % (self.sname(), self.children)
+                        )
+                    self.allow      = (name == "grant")
+                    self.privileges = child.children
+    
+                elif name == "inherited":
+                    self.inherited = str(child.children[0])
+    
+                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"
+                % (self.sname(), self.children)
+            )
+        assert self.invert is not None
+
+        if self.allow is None:
+            raise ValueError(
+                "One of DAV:grant or DAV:deny is required in %s, got: %s"
+                % (self.sname(), self.children)
+            )
+        assert self.privileges is not None
+
+# For DAV:principal element (RFC 3744, section 5.5.1) see Principal class above.
+
+# For DAV:all element (RFC 3744, section 5.5.1) see All class above.
+
+class Authenticated (WebDAVEmptyElement):
+    """
+    Principal which matches authenticated users. (RFC 3744, section 5.5.1)
+    """
+    name = "authenticated"
+
+class Unauthenticated (WebDAVEmptyElement):
+    """
+    Principal which matches unauthenticated users. (RFC 3744, section 5.5.1)
+    """
+    name = "unauthenticated"
+
+# For DAV:property element (RFC 3744, section 5.5.1) see Property class above.
+
+class Self (WebDAVEmptyElement):
+    """
+    Principal which matches a user if a resource is a principal and the user
+    matches the resource. (RFC 3744, sections 5.5.1 and 9.3)
+    """
+    name = "self"
+
+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)
+    """
+    name = "invert"
+
+    allowed_children = { (dav_namespace, "principal"): (1, 1) }
+
+class Grant (WebDAVElement):
+    """
+    Grants the contained privileges to a principal. (RFC 3744, section 5.5.2)
+    """
+    name = "grant"
+
+    allowed_children = { (dav_namespace, "privilege"): (1, None) }
+
+class Deny (WebDAVElement):
+    """
+    Denies the contained privileges to a principal. (RFC 3744, section 5.5.2)
+    """
+    name = "deny"
+
+    allowed_children = { (dav_namespace, "privilege"): (1, None) }
+
+# For DAV:privilege element (RFC 3744, section 5.5.2) see Privilege class above.
+
+class Protected (WebDAVEmptyElement):
+    """
+    Identifies an ACE as protected. (RFC 3744, section 5.5.3)
+    """
+    name = "protected"
+
+class Inherited (WebDAVElement):
+    """
+    Indicates that an ACE is inherited from the resource indentified by the
+    contained DAV:href element. (RFC 3744, section 5.5.4)
+    """
+    name = "inherited"
+
+    allowed_children = { (dav_namespace, "href"): (1, 1) }
+
+class ACLRestrictions (WebDAVElement):
+    """
+    Property which defines the types of ACLs supported by this server, to avoid
+    clients needlessly getting errors. (RFC 3744, section 5.6)
+    """
+    name = "acl-restrictions"
+    hidden = True
+    protected = True
+
+    allowed_children = {
+        (dav_namespace, "grant-only"        ): (0, 1),
+        (dav_namespace, "no-invert"         ): (0, 1),
+        (dav_namespace, "deny-before-grant" ): (0, 1),
+        (dav_namespace, "required-principal"): (0, 1),
+    }
+
+class GrantOnly (WebDAVEmptyElement):
+    """
+    Indicates that ACEs with deny clauses are not allowed. (RFC 3744, section
+    5.6.1)
+    """
+    name = "grant-only"
+
+class NoInvert (WebDAVEmptyElement):
+    """
+    Indicates that ACEs with the DAV:invert element are not allowed. (RFC 3744,
+    section 5.6.2)
+    """
+    name = "no-invert"
+
+class DenyBeforeGrant (WebDAVEmptyElement):
+    """
+    Indicates that all deny ACEs must precede all grant ACEs. (RFC 3744, section
+    5.6.3)
+    """
+    name = "deny-before-grant"
+
+class RequiredPrincipal (WebDAVElement):
+    """
+    Indicates which principals must have an ACE defined in an ACL. (RFC 3744,
+    section 5.6.4)
+    """
+    name = "required-principal"
+
+    allowed_children = {
+        (dav_namespace, "all"            ): (0, 1),
+        (dav_namespace, "authenticated"  ): (0, 1),
+        (dav_namespace, "unauthenticated"): (0, 1),
+        (dav_namespace, "self"           ): (0, 1),
+        (dav_namespace, "href"           ): (0, None),
+        (dav_namespace, "property"       ): (0, None),
+    }
+
+    def validate(self):
+        super(RequiredPrincipal, self).validate()
+
+        type = None
+
+        for child in self.children:
+            if type is None:
+                type = child.qname()
+            elif child.qname() != type:
+                raise ValueError(
+                    "Only one of DAV:all, DAV:authenticated, DAV:unauthenticated, "
+                    "DAV:self, DAV:href or DAV:property allowed for %s, got: %s"
+                    % (self.sname(), self.children)
+                )
+
+class InheritedACLSet (WebDAVElement):
+    """
+    Property which contains a set of URLs that identify other resources that
+    also control the access to this resource. (RFC 3744, section 5.7)
+    """
+    name = "inherited-acl-set"
+    hidden = True
+    protected = True
+
+    allowed_children = { (dav_namespace, "href"): (0, None) }
+
+class PrincipalCollectionSet (WebDAVElement):
+    """
+    Property which contains a set of URLs that identify the root collections
+    that contain the principals that are available on the server that implements
+    a resource. (RFC 3744, section 5.8)
+    """
+    name = "principal-collection-set"
+    hidden = True
+    protected = True
+
+    allowed_children = { (dav_namespace, "href"): (0, None) }
+
+##
+# Section 7 (Access Control and existing methods)
+##
+
+class NeedPrivileges (WebDAVElement):
+    """
+    Error which indicates insufficient privileges. (RFC 3744, section 7.1.1)
+    """
+    name = "need-privileges"
+
+    allowed_children = { (dav_namespace, "resource"): (0, None) }
+
+class Resource (WebDAVElement):
+    """
+    Identifies which resource had insufficient privileges. (RFC 3744, section
+    7.1.1)
+    """
+    name = "resource"
+
+    allowed_children = {
+        (dav_namespace, "href"     ): (1, 1),
+        (dav_namespace, "privilege"): (1, 1),
+    }
+
+##
+# Section 9 (Access Control Reports)
+##
+
+class ACLPrincipalPropSet (WebDAVElement):
+    """
+    Report which returns, for all principals in the DAV:acl property (of the
+    resource identified by the Request-URI) that are identified by http(s) URLs
+    or by a DAV:property principal, the value of the properties specified in the
+    REPORT request body. (RFC 3744, section 9.2)
+    """
+    name = "acl-principal-prop-set"
+
+    allowed_children = { WebDAVElement: (0, None) }
+
+    def validate(self):
+        super(ACLPrincipalPropSet, self).validate()
+
+        prop = False
+        
+        for child in self.children:
+            if child.qname() == (dav_namespace, "prop"):
+                if prop:
+                    raise ValueError(
+                        "Only one DAV:prop allowed for %s, got: %s"
+                        % (self.sname(), self.children)
+                    )
+                prop = True
+
+class PrincipalMatch (WebDAVElement):
+    """
+    Report used to identify all members (at any depth) of the collection
+    identified by the Request-URI that are principals and that match the current
+    user. (RFC 3744, section 9.3)
+    """
+    name = "principal-match"
+
+    allowed_children = {
+        (dav_namespace, "principal-property"): (0, 1),
+        (dav_namespace, "self"              ): (0, 1),
+        (dav_namespace, "prop"              ): (0, 1),
+    }
+
+    def validate(self):
+        super(PrincipalMatch, self).validate()
+
+        # This element can be empty when uses in supported-report-set
+        if not len(self.children):
+            return
+
+        principalPropertyOrSelf = False
+
+        for child in self.children:
+            namespace, name = child.qname()
+
+            if (namespace == dav_namespace) and name in ("principal-property", "self"):
+                if principalPropertyOrSelf:
+                    raise ValueError(
+                        "Only one of DAV:principal-property or DAV:self allowed in %s, got: %s"
+                        % (self.sname(), self.children)
+                    )
+                principalPropertyOrSelf = True
+
+        if not principalPropertyOrSelf:
+            raise ValueError(
+                "One of DAV:principal-property or DAV:self is required in %s, got: %s"
+                % (self.sname(), self.children)
+            )
+
+class PrincipalProperty (WebDAVElement):
+    """
+    Identifies a property. (RFC 3744, section 9.3)
+    """
+    name = "principal-property"
+
+    allowed_children = { WebDAVElement: (0, None) }
+
+# For DAV:self element (RFC 3744, section 9.3) see Self class above.
+
+class PrincipalPropertySearch (WebDAVElement):
+    """
+    Report which performs a search for all principals whose properties contain
+    character data that matches the search criteria specified in the request.
+    (RFC 3744, section 9.4)
+    """
+    name = "principal-property-search"
+
+    allowed_children = {
+        (dav_namespace, "property-search"                  ): (0, None),    # This is required but this element must be empty in supported-report-set
+        (dav_namespace, "prop"                             ): (0, 1),
+        (dav_namespace, "apply-to-principal-collection-set"): (0, 1),
+    }
+    allowed_attributes = { "test": False }
+
+class PropertySearch (WebDAVElement):
+    """
+    Contains a DAV:prop element enumerating the properties to be searched and a
+    DAV:match element, containing the search string. (RFC 3744, section 9.4)
+    """
+    name = "property-search"
+
+    allowed_children = {
+        (dav_namespace, "prop" ): (1, 1),
+        (dav_namespace, "match"): (1, 1),
+    }
+
+class Match (WebDAVTextElement):
+    """
+    Contains a search string. (RFC 3744, section 9.4)
+    """
+    name = "match"
+
+class PrincipalSearchPropertySet (WebDAVElement):
+    """
+    Report which identifies those properties that may be searched using the
+    DAV:principal-property-search report. (RFC 3744, section 9.5)
+    """
+    name = "principal-search-property-set"
+
+    allowed_children = { (dav_namespace, "principal-search-property"): (0, None) }
+
+class PrincipalSearchProperty (WebDAVElement):
+    """
+    Contains exactly one searchable property, and a description of the property.
+    (RFC 3744, section 9.5)
+    """
+    name = "principal-search-property"
+
+    allowed_children = {
+        (dav_namespace, "prop"       ): (1, 1),
+        (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.

Deleted: CalendarServer/trunk/twext/web2/dav/element/rfc4331.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/rfc4331.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/element/rfc4331.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,55 +0,0 @@
-##
-# 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 twext.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/twext/web2/dav/element/rfc4331.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/rfc4331.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/element/rfc4331.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/element/rfc4331.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -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 twext.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

Deleted: CalendarServer/trunk/twext/web2/dav/element/util.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/util.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/element/util.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,71 +0,0 @@
-##
-# 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
-##
-
-"""
-WebDAV XML utilities.
-
-This module provides XML utilities for use with WebDAV.
-
-See RFC 2518: http://www.ietf.org/rfc/rfc2518.txt (WebDAV)
-"""
-
-__all__ = [
-    "encodeXMLName",
-    "decodeXMLName",
-]
-
-def PrintXML(document, stream):
-    try:
-        import xml.dom.ext as ext
-    except ImportError:
-        import twext.web2.dav.element.xmlext as ext
-
-    document.normalize()
-    ext.Print(document, stream)
-    # For debugging, this is easier to read: (FIXME: disable for normal use)
-    #ext.PrettyPrint(document, stream)
-
-def encodeXMLName(name):
-    """
-    Encodes an XML (namespace, localname) pair into an ASCII string.
-    If namespace is None, returns localname encoded as UTF-8.
-    Otherwise, returns {namespace}localname encoded as UTF-8.
-    """
-    namespace, name = name
-    if namespace is None: return name.encode("utf-8")
-    return (u"{%s}%s" % (namespace, name)).encode("utf-8")
-
-def decodeXMLName(name):
-    """
-    Decodes an XML (namespace, localname) pair from an ASCII string as encoded
-    by encodeXMLName().
-    """
-    if name[0] is not "{": return (None, name.decode("utf-8"))
-
-    index = name.find("}")
-
-    if (index is -1 or not len(name) > index):
-        raise ValueError("Invalid encoded name: %r" % (name,))
-
-    return (name[1:index].decode("utf-8"), name[index+1:].decode("utf-8"))

Copied: CalendarServer/trunk/twext/web2/dav/element/util.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/util.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/element/util.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/element/util.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,71 @@
+##
+# 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
+##
+
+"""
+WebDAV XML utilities.
+
+This module provides XML utilities for use with WebDAV.
+
+See RFC 2518: http://www.ietf.org/rfc/rfc2518.txt (WebDAV)
+"""
+
+__all__ = [
+    "encodeXMLName",
+    "decodeXMLName",
+]
+
+def PrintXML(document, stream):
+    try:
+        import xml.dom.ext as ext
+    except ImportError:
+        import twext.web2.dav.element.xmlext as ext
+
+    document.normalize()
+    ext.Print(document, stream)
+    # For debugging, this is easier to read: (FIXME: disable for normal use)
+    #ext.PrettyPrint(document, stream)
+
+def encodeXMLName(name):
+    """
+    Encodes an XML (namespace, localname) pair into an ASCII string.
+    If namespace is None, returns localname encoded as UTF-8.
+    Otherwise, returns {namespace}localname encoded as UTF-8.
+    """
+    namespace, name = name
+    if namespace is None: return name.encode("utf-8")
+    return (u"{%s}%s" % (namespace, name)).encode("utf-8")
+
+def decodeXMLName(name):
+    """
+    Decodes an XML (namespace, localname) pair from an ASCII string as encoded
+    by encodeXMLName().
+    """
+    if name[0] is not "{": return (None, name.decode("utf-8"))
+
+    index = name.find("}")
+
+    if (index is -1 or not len(name) > index):
+        raise ValueError("Invalid encoded name: %r" % (name,))
+
+    return (name[1:index].decode("utf-8"), name[index+1:].decode("utf-8"))

Deleted: CalendarServer/trunk/twext/web2/dav/element/xmlext.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/xmlext.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/element/xmlext.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,430 +0,0 @@
-########################################################################
-#
-# File Name:            __init__.py
-#
-#
-"""
-WWW: http://4suite.com/4DOM         e-mail: support at 4suite.com
-
-Copyright (c) 2000 Fourthought Inc, USA.   All Rights Reserved.
-See  http://4suite.com/COPYRIGHT  for license and copyright information
-"""
-
-"""Some Helper functions: 4DOM/PyXML-specific Extensions to the DOM,
-and DOM-related utilities."""
-
-__all__ = [ "Print", "PrettyPrint" ]
-
-import sys,string
-import re
-
-from xml.dom import Node
-from xml.dom import XML_NAMESPACE, XMLNS_NAMESPACE, DOMException
-
-def Print(root, stream=sys.stdout, encoding='UTF-8'):
-    if not hasattr(root, "nodeType"):
-        return
-    nss = SeekNss(root)
-    visitor = PrintVisitor(stream, encoding, nsHints=nss)
-    PrintWalker(visitor, root).run()
-    return
-
-def PrettyPrint(root, stream=sys.stdout, encoding='UTF-8', indent='  ',
-                preserveElements=None):
-    if not hasattr(root, "nodeType"):
-        return
-    nss_hints = SeekNss(root)
-    preserveElements = preserveElements or []
-    owner_doc = root.ownerDocument or root
-    if hasattr(owner_doc, 'getElementsByName'):
-        #We don't want to insert any whitespace into HTML inline elements
-        preserveElements = preserveElements + HTML_4_TRANSITIONAL_INLINE
-    visitor = PrintVisitor(stream, encoding, indent,
-                                   preserveElements, nss_hints)
-    PrintWalker(visitor, root).run()
-    stream.write('\n')
-    return
-
-def GetAllNs(node):
-    #The xml namespace is implicit
-    nss = {'xml': XML_NAMESPACE}
-    if node.nodeType == Node.ATTRIBUTE_NODE and node.ownerElement:
-        return GetAllNs(node.ownerElement)
-    if node.nodeType == Node.ELEMENT_NODE:
-        if node.namespaceURI:
-            nss[node.prefix] = node.namespaceURI
-        for attr in node.attributes.values():
-            if attr.namespaceURI == XMLNS_NAMESPACE:
-                if attr.localName == 'xmlns':
-                    nss[None] = attr.value
-                else:
-                    nss[attr.localName] = attr.value
-            elif attr.namespaceURI:
-                nss[attr.prefix] = attr.namespaceURI
-    if node.parentNode:
-        #Inner NS/Prefix mappings take precedence over outer ones
-        parent_nss = GetAllNs(node.parentNode)
-        parent_nss.update(nss)
-        nss = parent_nss
-    return nss
-
-def SeekNss(node, nss=None):
-    '''traverses the tree to seek an approximate set of defined namespaces'''
-    nss = nss or {}
-    for child in node.childNodes:
-        if child.nodeType == Node.ELEMENT_NODE:
-            if child.namespaceURI:
-                nss[child.prefix] = child.namespaceURI
-            for attr in child.attributes.values():
-                if attr.namespaceURI == XMLNS_NAMESPACE:
-                    if attr.localName == 'xmlns':
-                        nss[None] = attr.value
-                    else:
-                        nss[attr.localName] = attr.value
-                elif attr.namespaceURI:
-                    nss[attr.prefix] = attr.namespaceURI
-            SeekNss(child, nss)
-    return nss
-
-class PrintVisitor:
-    def __init__(self, stream, encoding, indent='', plainElements=None,
-                 nsHints=None, isXhtml=0, force8bit=0):
-        self.stream = stream
-        self.encoding = encoding
-        # Namespaces
-        self._namespaces = [{}]
-        self._nsHints = nsHints or {}
-        # PrettyPrint
-        self._indent = indent
-        self._depth = 0
-        self._inText = 0
-        self._plainElements = plainElements or []
-        # HTML support
-        self._html = None
-        self._isXhtml = isXhtml
-        self.force8bit = force8bit
-        return
-
-    def _write(self, text):
-        if self.force8bit:
-            obj = strobj_to_utf8str(text, self.encoding)
-        else:
-            obj = utf8_to_code(text, self.encoding)
-        self.stream.write(obj)
-        return
-
-    def _tryIndent(self):
-        if not self._inText and self._indent:
-            self._write('\n' + self._indent*self._depth)
-        return
-
-    def visit(self, node):
-        if self._html is None:
-            # Set HTMLDocument flag here for speed
-            self._html = hasattr(node.ownerDocument, 'getElementsByName')
-
-        nodeType = node.nodeType
-        if node.nodeType == Node.ELEMENT_NODE:
-            return self.visitElement(node)
-
-        elif node.nodeType == Node.ATTRIBUTE_NODE:
-            return self.visitAttr(node)
-
-        elif node.nodeType == Node.TEXT_NODE:
-            return self.visitText(node)
-
-        elif node.nodeType == Node.CDATA_SECTION_NODE:
-            return self.visitCDATASection(node)
-
-        elif node.nodeType == Node.ENTITY_REFERENCE_NODE:
-            return self.visitEntityReference(node)
-
-        elif node.nodeType == Node.ENTITY_NODE:
-            return self.visitEntity(node)
-
-        elif node.nodeType == Node.PROCESSING_INSTRUCTION_NODE:
-            return self.visitProcessingInstruction(node)
-
-        elif node.nodeType == Node.COMMENT_NODE:
-            return self.visitComment(node)
-
-        elif node.nodeType == Node.DOCUMENT_NODE:
-            return self.visitDocument(node)
-
-        elif node.nodeType == Node.DOCUMENT_TYPE_NODE:
-            return self.visitDocumentType(node)
-
-        elif node.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
-            return self.visitDocumentFragment(node)
-
-        elif node.nodeType == Node.NOTATION_NODE:
-            return self.visitNotation(node)
-
-        # It has a node type, but we don't know how to handle it
-        raise Exception("Unknown node type: %s" % repr(node))
-
-    def visitNodeList(self, node, exclude=None):
-        for curr in node:
-            curr is not exclude and self.visit(curr)
-        return
-
-    def visitNamedNodeMap(self, node):
-        for item in node.values():
-            self.visit(item)
-        return
-
-    def visitAttr(self, node):
-        if node.namespaceURI == XMLNS_NAMESPACE:
-            # Skip namespace declarations
-            return
-        self._write(' ' + node.name)
-        value = node.value
-        if value or not self._html:
-            text = TranslateCdata(value, self.encoding)
-            text, delimiter = TranslateCdataAttr(text)
-            self.stream.write("=%s%s%s" % (delimiter, text, delimiter))
-        return
-
-    def visitProlog(self):
-        self._write("<?xml version='1.0' encoding='%s'?>" % (
-            self.encoding or 'utf-8'
-            ))
-        self._inText = 0
-        return
-
-    def visitDocument(self, node):
-        not self._html and self.visitProlog()
-        node.doctype and self.visitDocumentType(node.doctype)
-        self.visitNodeList(node.childNodes, exclude=node.doctype)
-        return
-
-    def visitDocumentFragment(self, node):
-        self.visitNodeList(node.childNodes)
-        return
-
-    def visitElement(self, node):
-        self._namespaces.append(self._namespaces[-1].copy())
-        inline = node.tagName in self._plainElements
-        not inline and self._tryIndent()
-        self._write('<%s' % node.tagName)
-        if self._isXhtml or not self._html:
-            namespaces = ''
-            if self._isXhtml:
-                nss = {'xml': XML_NAMESPACE, None: XHTML_NAMESPACE}
-            else:
-                nss = GetAllNs(node)
-            if self._nsHints:
-                self._nsHints.update(nss)
-                nss = self._nsHints
-                self._nsHints = {}
-            del nss['xml']
-            for prefix in nss.keys():
-                if not self._namespaces[-1].has_key(prefix) or self._namespaces[-1][prefix] != nss[prefix]:
-                    nsuri, delimiter = TranslateCdataAttr(nss[prefix])
-                    if prefix:
-                        xmlns = " xmlns:%s=%s%s%s" % (prefix, delimiter,nsuri,delimiter)
-                    else:
-                        xmlns = " xmlns=%s%s%s" % (delimiter,nsuri,delimiter)
-                    namespaces = namespaces + xmlns
-
-                self._namespaces[-1][prefix] = nss[prefix]
-            self._write(namespaces)
-        for attr in node.attributes.values():
-            self.visitAttr(attr)
-        if len(node.childNodes):
-            self._write('>')
-            self._depth = self._depth + 1
-            self.visitNodeList(node.childNodes)
-            self._depth = self._depth - 1
-            if not self._html or (node.tagName not in HTML_FORBIDDEN_END):
-                not (self._inText and inline) and self._tryIndent()
-                self._write('</%s>' % node.tagName)
-        elif not self._html:
-            self._write('/>')
-        elif node.tagName not in HTML_FORBIDDEN_END:
-            self._write('></%s>' % node.tagName)
-        else:
-            self._write('>')
-        del self._namespaces[-1]
-        self._inText = 0
-        return
-
-    def visitText(self, node):
-        text = node.data
-        if self._indent:
-            text = string.strip(text) and text
-        if text:
-            if self._html:
-                text = TranslateHtmlCdata(text, self.encoding)
-            else:
-                text = TranslateCdata(text, self.encoding)
-            self.stream.write(text)
-            self._inText = 1
-        return
-
-    def visitDocumentType(self, doctype):
-        if not doctype.systemId and not doctype.publicId: return
-        self._tryIndent()
-        self._write('<!DOCTYPE %s' % doctype.name)
-        if doctype.systemId and '"' in doctype.systemId:
-            system = "'%s'" % doctype.systemId
-        else:
-            system = '"%s"' % doctype.systemId
-        if doctype.publicId and '"' in doctype.publicId:
-            # We should probably throw an error
-            # Valid characters:  <space> | <newline> | <linefeed> |
-            #                    [a-zA-Z0-9] | [-'()+,./:=?;!*#@$_%]
-            public = "'%s'" % doctype.publicId
-        else:
-            public = '"%s"' % doctype.publicId
-        if doctype.publicId and doctype.systemId:
-            self._write(' PUBLIC %s %s' % (public, system))
-        elif doctype.systemId:
-            self._write(' SYSTEM %s' % system)
-        if doctype.entities or doctype.notations:
-            self._write(' [')
-            self._depth = self._depth + 1
-            self.visitNamedNodeMap(doctype.entities)
-            self.visitNamedNodeMap(doctype.notations)
-            self._depth = self._depth - 1
-            self._tryIndent()
-            self._write(']>')
-        else:
-            self._write('>')
-        self._inText = 0
-        return
-
-    def visitEntity(self, node):
-        """Visited from a NamedNodeMap in DocumentType"""
-        self._tryIndent()
-        self._write('<!ENTITY %s' % (node.nodeName))
-        node.publicId and self._write(' PUBLIC %s' % node.publicId)
-        node.systemId and self._write(' SYSTEM %s' % node.systemId)
-        node.notationName and self._write(' NDATA %s' % node.notationName)
-        self._write('>')
-        return
-
-    def visitNotation(self, node):
-        """Visited from a NamedNodeMap in DocumentType"""
-        self._tryIndent()
-        self._write('<!NOTATION %s' % node.nodeName)
-        node.publicId and self._write(' PUBLIC %s' % node.publicId)
-        node.systemId and self._write(' SYSTEM %s' % node.systemId)
-        self._write('>')
-        return
-
-    def visitCDATASection(self, node):
-        self._tryIndent()
-        self._write('<![CDATA[%s]]>' % (node.data))
-        self._inText = 0
-        return
-
-    def visitComment(self, node):
-        self._tryIndent()
-        self._write('<!--%s-->' % (node.data))
-        self._inText = 0
-        return
-
-    def visitEntityReference(self, node):
-        self._write('&%s;' % node.nodeName)
-        self._inText = 1
-        return
-
-    def visitProcessingInstruction(self, node):
-        self._tryIndent()
-        self._write('<?%s %s?>' % (node.target, node.data))
-        self._inText = 0
-        return
-
-class PrintWalker:
-    def __init__(self, visitor, startNode):
-        self.visitor = visitor
-        self.start_node = startNode
-        return
-
-    def step(self):
-        """There is really no step to printing.  It prints the whole thing"""
-        self.visitor.visit(self.start_node)
-        return
-
-    def run(self):
-        return self.step()
-
-ILLEGAL_LOW_CHARS = '[\x01-\x08\x0B-\x0C\x0E-\x1F]'
-SURROGATE_BLOCK = '[\xF0-\xF7][\x80-\xBF][\x80-\xBF][\x80-\xBF]'
-ILLEGAL_HIGH_CHARS = '\xEF\xBF[\xBE\xBF]'
-#Note: Prolly fuzzy on this, but it looks as if characters from the surrogate block are allowed if in scalar form, which is encoded in UTF8 the same was as in surrogate block form
-XML_ILLEGAL_CHAR_PATTERN = re.compile('%s|%s'%(ILLEGAL_LOW_CHARS, ILLEGAL_HIGH_CHARS))
-
-g_utf8TwoBytePattern = re.compile('([\xC0-\xC3])([\x80-\xBF])')
-g_cdataCharPattern = re.compile('[&<]|]]>')
-g_charToEntity = {
-        '&': '&amp;',
-        '<': '&lt;',
-        ']]>': ']]&gt;',
-        }
-
-# Slightly modified to not use types.Unicode
-import codecs
-def utf8_to_code(text, encoding):
-    encoder = codecs.lookup(encoding)[0] # encode,decode,reader,writer
-    if type(text) is not unicode:
-        text = unicode(text, "utf-8")
-    return encoder(text)[0] # result,size
-def strobj_to_utf8str(text, encoding):
-    if string.upper(encoding) not in ["UTF-8", "ISO-8859-1", "LATIN-1"]:
-        raise ValueError("Invalid encoding: %s"%encoding)
-    encoder = codecs.lookup(encoding)[0] # encode,decode,reader,writer
-    if type(text) is not unicode:
-        text = unicode(text, "utf-8")
-    #FIXME
-    return str(encoder(text)[0])
-
-def TranslateCdataAttr(characters):
-    '''Handles normalization and some intelligence about quoting'''
-    if not characters:
-        return '', "'"
-    if "'" in characters:
-        delimiter = '"'
-        new_chars = re.sub('"', '&quot;', characters)
-    else:
-        delimiter = "'"
-        new_chars = re.sub("'", '&apos;', characters)
-    #FIXME: There's more to normalization
-    #Convert attribute new-lines to character entity
-    # characters is possibly shorter than new_chars (no entities)
-    if "\n" in characters:
-        new_chars = re.sub('\n', '&#10;', new_chars)
-    return new_chars, delimiter
-
-#Note: Unicode object only for now
-def TranslateCdata(characters, encoding='UTF-8', prev_chars='', markupSafe=0,
-                   charsetHandler=utf8_to_code):
-    """
-    charsetHandler is a function that takes a string or unicode object as the
-    first argument, representing the string to be procesed, and an encoding
-    specifier as the second argument.  It must return a string or unicode
-    object
-    """
-    if not characters:
-        return ''
-    if not markupSafe:
-        if g_cdataCharPattern.search(characters):
-            new_string = g_cdataCharPattern.subn(
-                lambda m, d=g_charToEntity: d[m.group()],
-                characters)[0]
-        else:
-            new_string = characters
-        if prev_chars[-2:] == ']]' and characters[0] == '>':
-            new_string = '&gt;' + new_string[1:]
-    else:
-        new_string = characters
-    #Note: use decimal char entity rep because some browsers are broken
-    #FIXME: This will bomb for high characters.  Should, for instance, detect
-    #The UTF-8 for 0xFFFE and put out &#xFFFE;
-    if XML_ILLEGAL_CHAR_PATTERN.search(new_string):
-        new_string = XML_ILLEGAL_CHAR_PATTERN.subn(
-            lambda m: '&#%i;' % ord(m.group()),
-            new_string)[0]
-    new_string = charsetHandler(new_string, encoding)
-    return new_string

Copied: CalendarServer/trunk/twext/web2/dav/element/xmlext.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/element/xmlext.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/element/xmlext.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/element/xmlext.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,430 @@
+########################################################################
+#
+# File Name:            __init__.py
+#
+#
+"""
+WWW: http://4suite.com/4DOM         e-mail: support at 4suite.com
+
+Copyright (c) 2000 Fourthought Inc, USA.   All Rights Reserved.
+See  http://4suite.com/COPYRIGHT  for license and copyright information
+"""
+
+"""Some Helper functions: 4DOM/PyXML-specific Extensions to the DOM,
+and DOM-related utilities."""
+
+__all__ = [ "Print", "PrettyPrint" ]
+
+import sys,string
+import re
+
+from xml.dom import Node
+from xml.dom import XML_NAMESPACE, XMLNS_NAMESPACE, DOMException
+
+def Print(root, stream=sys.stdout, encoding='UTF-8'):
+    if not hasattr(root, "nodeType"):
+        return
+    nss = SeekNss(root)
+    visitor = PrintVisitor(stream, encoding, nsHints=nss)
+    PrintWalker(visitor, root).run()
+    return
+
+def PrettyPrint(root, stream=sys.stdout, encoding='UTF-8', indent='  ',
+                preserveElements=None):
+    if not hasattr(root, "nodeType"):
+        return
+    nss_hints = SeekNss(root)
+    preserveElements = preserveElements or []
+    owner_doc = root.ownerDocument or root
+    if hasattr(owner_doc, 'getElementsByName'):
+        #We don't want to insert any whitespace into HTML inline elements
+        preserveElements = preserveElements + HTML_4_TRANSITIONAL_INLINE
+    visitor = PrintVisitor(stream, encoding, indent,
+                                   preserveElements, nss_hints)
+    PrintWalker(visitor, root).run()
+    stream.write('\n')
+    return
+
+def GetAllNs(node):
+    #The xml namespace is implicit
+    nss = {'xml': XML_NAMESPACE}
+    if node.nodeType == Node.ATTRIBUTE_NODE and node.ownerElement:
+        return GetAllNs(node.ownerElement)
+    if node.nodeType == Node.ELEMENT_NODE:
+        if node.namespaceURI:
+            nss[node.prefix] = node.namespaceURI
+        for attr in node.attributes.values():
+            if attr.namespaceURI == XMLNS_NAMESPACE:
+                if attr.localName == 'xmlns':
+                    nss[None] = attr.value
+                else:
+                    nss[attr.localName] = attr.value
+            elif attr.namespaceURI:
+                nss[attr.prefix] = attr.namespaceURI
+    if node.parentNode:
+        #Inner NS/Prefix mappings take precedence over outer ones
+        parent_nss = GetAllNs(node.parentNode)
+        parent_nss.update(nss)
+        nss = parent_nss
+    return nss
+
+def SeekNss(node, nss=None):
+    '''traverses the tree to seek an approximate set of defined namespaces'''
+    nss = nss or {}
+    for child in node.childNodes:
+        if child.nodeType == Node.ELEMENT_NODE:
+            if child.namespaceURI:
+                nss[child.prefix] = child.namespaceURI
+            for attr in child.attributes.values():
+                if attr.namespaceURI == XMLNS_NAMESPACE:
+                    if attr.localName == 'xmlns':
+                        nss[None] = attr.value
+                    else:
+                        nss[attr.localName] = attr.value
+                elif attr.namespaceURI:
+                    nss[attr.prefix] = attr.namespaceURI
+            SeekNss(child, nss)
+    return nss
+
+class PrintVisitor:
+    def __init__(self, stream, encoding, indent='', plainElements=None,
+                 nsHints=None, isXhtml=0, force8bit=0):
+        self.stream = stream
+        self.encoding = encoding
+        # Namespaces
+        self._namespaces = [{}]
+        self._nsHints = nsHints or {}
+        # PrettyPrint
+        self._indent = indent
+        self._depth = 0
+        self._inText = 0
+        self._plainElements = plainElements or []
+        # HTML support
+        self._html = None
+        self._isXhtml = isXhtml
+        self.force8bit = force8bit
+        return
+
+    def _write(self, text):
+        if self.force8bit:
+            obj = strobj_to_utf8str(text, self.encoding)
+        else:
+            obj = utf8_to_code(text, self.encoding)
+        self.stream.write(obj)
+        return
+
+    def _tryIndent(self):
+        if not self._inText and self._indent:
+            self._write('\n' + self._indent*self._depth)
+        return
+
+    def visit(self, node):
+        if self._html is None:
+            # Set HTMLDocument flag here for speed
+            self._html = hasattr(node.ownerDocument, 'getElementsByName')
+
+        nodeType = node.nodeType
+        if node.nodeType == Node.ELEMENT_NODE:
+            return self.visitElement(node)
+
+        elif node.nodeType == Node.ATTRIBUTE_NODE:
+            return self.visitAttr(node)
+
+        elif node.nodeType == Node.TEXT_NODE:
+            return self.visitText(node)
+
+        elif node.nodeType == Node.CDATA_SECTION_NODE:
+            return self.visitCDATASection(node)
+
+        elif node.nodeType == Node.ENTITY_REFERENCE_NODE:
+            return self.visitEntityReference(node)
+
+        elif node.nodeType == Node.ENTITY_NODE:
+            return self.visitEntity(node)
+
+        elif node.nodeType == Node.PROCESSING_INSTRUCTION_NODE:
+            return self.visitProcessingInstruction(node)
+
+        elif node.nodeType == Node.COMMENT_NODE:
+            return self.visitComment(node)
+
+        elif node.nodeType == Node.DOCUMENT_NODE:
+            return self.visitDocument(node)
+
+        elif node.nodeType == Node.DOCUMENT_TYPE_NODE:
+            return self.visitDocumentType(node)
+
+        elif node.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
+            return self.visitDocumentFragment(node)
+
+        elif node.nodeType == Node.NOTATION_NODE:
+            return self.visitNotation(node)
+
+        # It has a node type, but we don't know how to handle it
+        raise Exception("Unknown node type: %s" % repr(node))
+
+    def visitNodeList(self, node, exclude=None):
+        for curr in node:
+            curr is not exclude and self.visit(curr)
+        return
+
+    def visitNamedNodeMap(self, node):
+        for item in node.values():
+            self.visit(item)
+        return
+
+    def visitAttr(self, node):
+        if node.namespaceURI == XMLNS_NAMESPACE:
+            # Skip namespace declarations
+            return
+        self._write(' ' + node.name)
+        value = node.value
+        if value or not self._html:
+            text = TranslateCdata(value, self.encoding)
+            text, delimiter = TranslateCdataAttr(text)
+            self.stream.write("=%s%s%s" % (delimiter, text, delimiter))
+        return
+
+    def visitProlog(self):
+        self._write("<?xml version='1.0' encoding='%s'?>" % (
+            self.encoding or 'utf-8'
+            ))
+        self._inText = 0
+        return
+
+    def visitDocument(self, node):
+        not self._html and self.visitProlog()
+        node.doctype and self.visitDocumentType(node.doctype)
+        self.visitNodeList(node.childNodes, exclude=node.doctype)
+        return
+
+    def visitDocumentFragment(self, node):
+        self.visitNodeList(node.childNodes)
+        return
+
+    def visitElement(self, node):
+        self._namespaces.append(self._namespaces[-1].copy())
+        inline = node.tagName in self._plainElements
+        not inline and self._tryIndent()
+        self._write('<%s' % node.tagName)
+        if self._isXhtml or not self._html:
+            namespaces = ''
+            if self._isXhtml:
+                nss = {'xml': XML_NAMESPACE, None: XHTML_NAMESPACE}
+            else:
+                nss = GetAllNs(node)
+            if self._nsHints:
+                self._nsHints.update(nss)
+                nss = self._nsHints
+                self._nsHints = {}
+            del nss['xml']
+            for prefix in nss.keys():
+                if not self._namespaces[-1].has_key(prefix) or self._namespaces[-1][prefix] != nss[prefix]:
+                    nsuri, delimiter = TranslateCdataAttr(nss[prefix])
+                    if prefix:
+                        xmlns = " xmlns:%s=%s%s%s" % (prefix, delimiter,nsuri,delimiter)
+                    else:
+                        xmlns = " xmlns=%s%s%s" % (delimiter,nsuri,delimiter)
+                    namespaces = namespaces + xmlns
+
+                self._namespaces[-1][prefix] = nss[prefix]
+            self._write(namespaces)
+        for attr in node.attributes.values():
+            self.visitAttr(attr)
+        if len(node.childNodes):
+            self._write('>')
+            self._depth = self._depth + 1
+            self.visitNodeList(node.childNodes)
+            self._depth = self._depth - 1
+            if not self._html or (node.tagName not in HTML_FORBIDDEN_END):
+                not (self._inText and inline) and self._tryIndent()
+                self._write('</%s>' % node.tagName)
+        elif not self._html:
+            self._write('/>')
+        elif node.tagName not in HTML_FORBIDDEN_END:
+            self._write('></%s>' % node.tagName)
+        else:
+            self._write('>')
+        del self._namespaces[-1]
+        self._inText = 0
+        return
+
+    def visitText(self, node):
+        text = node.data
+        if self._indent:
+            text = string.strip(text) and text
+        if text:
+            if self._html:
+                text = TranslateHtmlCdata(text, self.encoding)
+            else:
+                text = TranslateCdata(text, self.encoding)
+            self.stream.write(text)
+            self._inText = 1
+        return
+
+    def visitDocumentType(self, doctype):
+        if not doctype.systemId and not doctype.publicId: return
+        self._tryIndent()
+        self._write('<!DOCTYPE %s' % doctype.name)
+        if doctype.systemId and '"' in doctype.systemId:
+            system = "'%s'" % doctype.systemId
+        else:
+            system = '"%s"' % doctype.systemId
+        if doctype.publicId and '"' in doctype.publicId:
+            # We should probably throw an error
+            # Valid characters:  <space> | <newline> | <linefeed> |
+            #                    [a-zA-Z0-9] | [-'()+,./:=?;!*#@$_%]
+            public = "'%s'" % doctype.publicId
+        else:
+            public = '"%s"' % doctype.publicId
+        if doctype.publicId and doctype.systemId:
+            self._write(' PUBLIC %s %s' % (public, system))
+        elif doctype.systemId:
+            self._write(' SYSTEM %s' % system)
+        if doctype.entities or doctype.notations:
+            self._write(' [')
+            self._depth = self._depth + 1
+            self.visitNamedNodeMap(doctype.entities)
+            self.visitNamedNodeMap(doctype.notations)
+            self._depth = self._depth - 1
+            self._tryIndent()
+            self._write(']>')
+        else:
+            self._write('>')
+        self._inText = 0
+        return
+
+    def visitEntity(self, node):
+        """Visited from a NamedNodeMap in DocumentType"""
+        self._tryIndent()
+        self._write('<!ENTITY %s' % (node.nodeName))
+        node.publicId and self._write(' PUBLIC %s' % node.publicId)
+        node.systemId and self._write(' SYSTEM %s' % node.systemId)
+        node.notationName and self._write(' NDATA %s' % node.notationName)
+        self._write('>')
+        return
+
+    def visitNotation(self, node):
+        """Visited from a NamedNodeMap in DocumentType"""
+        self._tryIndent()
+        self._write('<!NOTATION %s' % node.nodeName)
+        node.publicId and self._write(' PUBLIC %s' % node.publicId)
+        node.systemId and self._write(' SYSTEM %s' % node.systemId)
+        self._write('>')
+        return
+
+    def visitCDATASection(self, node):
+        self._tryIndent()
+        self._write('<![CDATA[%s]]>' % (node.data))
+        self._inText = 0
+        return
+
+    def visitComment(self, node):
+        self._tryIndent()
+        self._write('<!--%s-->' % (node.data))
+        self._inText = 0
+        return
+
+    def visitEntityReference(self, node):
+        self._write('&%s;' % node.nodeName)
+        self._inText = 1
+        return
+
+    def visitProcessingInstruction(self, node):
+        self._tryIndent()
+        self._write('<?%s %s?>' % (node.target, node.data))
+        self._inText = 0
+        return
+
+class PrintWalker:
+    def __init__(self, visitor, startNode):
+        self.visitor = visitor
+        self.start_node = startNode
+        return
+
+    def step(self):
+        """There is really no step to printing.  It prints the whole thing"""
+        self.visitor.visit(self.start_node)
+        return
+
+    def run(self):
+        return self.step()
+
+ILLEGAL_LOW_CHARS = '[\x01-\x08\x0B-\x0C\x0E-\x1F]'
+SURROGATE_BLOCK = '[\xF0-\xF7][\x80-\xBF][\x80-\xBF][\x80-\xBF]'
+ILLEGAL_HIGH_CHARS = '\xEF\xBF[\xBE\xBF]'
+#Note: Prolly fuzzy on this, but it looks as if characters from the surrogate block are allowed if in scalar form, which is encoded in UTF8 the same was as in surrogate block form
+XML_ILLEGAL_CHAR_PATTERN = re.compile('%s|%s'%(ILLEGAL_LOW_CHARS, ILLEGAL_HIGH_CHARS))
+
+g_utf8TwoBytePattern = re.compile('([\xC0-\xC3])([\x80-\xBF])')
+g_cdataCharPattern = re.compile('[&<]|]]>')
+g_charToEntity = {
+        '&': '&amp;',
+        '<': '&lt;',
+        ']]>': ']]&gt;',
+        }
+
+# Slightly modified to not use types.Unicode
+import codecs
+def utf8_to_code(text, encoding):
+    encoder = codecs.lookup(encoding)[0] # encode,decode,reader,writer
+    if type(text) is not unicode:
+        text = unicode(text, "utf-8")
+    return encoder(text)[0] # result,size
+def strobj_to_utf8str(text, encoding):
+    if string.upper(encoding) not in ["UTF-8", "ISO-8859-1", "LATIN-1"]:
+        raise ValueError("Invalid encoding: %s"%encoding)
+    encoder = codecs.lookup(encoding)[0] # encode,decode,reader,writer
+    if type(text) is not unicode:
+        text = unicode(text, "utf-8")
+    #FIXME
+    return str(encoder(text)[0])
+
+def TranslateCdataAttr(characters):
+    '''Handles normalization and some intelligence about quoting'''
+    if not characters:
+        return '', "'"
+    if "'" in characters:
+        delimiter = '"'
+        new_chars = re.sub('"', '&quot;', characters)
+    else:
+        delimiter = "'"
+        new_chars = re.sub("'", '&apos;', characters)
+    #FIXME: There's more to normalization
+    #Convert attribute new-lines to character entity
+    # characters is possibly shorter than new_chars (no entities)
+    if "\n" in characters:
+        new_chars = re.sub('\n', '&#10;', new_chars)
+    return new_chars, delimiter
+
+#Note: Unicode object only for now
+def TranslateCdata(characters, encoding='UTF-8', prev_chars='', markupSafe=0,
+                   charsetHandler=utf8_to_code):
+    """
+    charsetHandler is a function that takes a string or unicode object as the
+    first argument, representing the string to be procesed, and an encoding
+    specifier as the second argument.  It must return a string or unicode
+    object
+    """
+    if not characters:
+        return ''
+    if not markupSafe:
+        if g_cdataCharPattern.search(characters):
+            new_string = g_cdataCharPattern.subn(
+                lambda m, d=g_charToEntity: d[m.group()],
+                characters)[0]
+        else:
+            new_string = characters
+        if prev_chars[-2:] == ']]' and characters[0] == '>':
+            new_string = '&gt;' + new_string[1:]
+    else:
+        new_string = characters
+    #Note: use decimal char entity rep because some browsers are broken
+    #FIXME: This will bomb for high characters.  Should, for instance, detect
+    #The UTF-8 for 0xFFFE and put out &#xFFFE;
+    if XML_ILLEGAL_CHAR_PATTERN.search(new_string):
+        new_string = XML_ILLEGAL_CHAR_PATTERN.subn(
+            lambda m: '&#%i;' % ord(m.group()),
+            new_string)[0]
+    new_string = charsetHandler(new_string, encoding)
+    return new_string

Copied: CalendarServer/trunk/twext/web2/dav/fileop.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/fileop.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/fileop.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/fileop.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,512 @@
+##
+# 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
+##
+
+"""
+WebDAV file operations
+
+This API is considered private to static.py and is therefore subject to
+change.
+"""
+
+__all__ = [
+    "delete",
+    "copy",
+    "move",
+    "put",
+    "mkcollection",
+    "rmdir",
+]
+
+import os
+import urllib
+from urlparse import urlsplit
+
+from twisted.python import log
+from twext.python.filepath import CachingFilePath as FilePath
+from twisted.python.failure import Failure
+from twisted.internet.defer import succeed, deferredGenerator, waitForDeferred
+from twext.web2 import responsecode
+from twext.web2.http import StatusResponse, HTTPError
+from twext.web2.stream import FileStream, readIntoFile
+from twext.web2.dav.http import ResponseQueue, statusForFailure
+
+def delete(uri, filepath, depth="infinity"):
+    """
+    Perform a X{DELETE} operation on the given URI, which is backed by the given
+    filepath.
+    @param filepath: the L{FilePath} to delete.
+    @param depth: the recursion X{Depth} for the X{DELETE} operation, which must
+        be "infinity".
+    @raise HTTPError: (containing a response with a status code of
+        L{responsecode.BAD_REQUEST}) if C{depth} is not "infinity".
+    @raise HTTPError: (containing an appropriate response) if the
+        delete operation fails.  If C{filepath} is a directory, the response
+        will be a L{MultiStatusResponse}.
+    @return: a deferred response with a status code of L{responsecode.NO_CONTENT}
+        if the X{DELETE} operation succeeds.
+    """
+    #
+    # Remove the file(s)
+    #
+    # FIXME: defer
+    if filepath.isdir():
+        #
+        # RFC 2518, section 8.6 says that we must act as if the Depth header is
+        # set to infinity, and that the client must omit the Depth header or set
+        # it to infinity, meaning that for collections, we will delete all
+        # members.
+        #
+        # This seems somewhat at odds with the notion that a bad request should
+        # be rejected outright; if the client sends a bad depth header, the
+        # client is broken, and RFC 2518, section 8 suggests that a bad request
+        # should be rejected...
+        #
+        # Let's play it safe for now and ignore broken clients.
+        #
+
+        if depth != "infinity":
+            msg = ("Client sent illegal depth header value for DELETE: %s" % (depth,))
+            log.err(msg)
+            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
+
+        #
+        # Recursive delete
+        #
+        # RFC 2518, section 8.6 says that if we get an error deleting a resource
+        # other than the collection in the request-URI, that we must respond
+        # with a multi-status response containing error statuses for each
+        # resource that we fail to delete.  It also says we should not return
+        # no-content (success) status, which means that we should continue after
+        # errors, rather than aborting right away.  This is interesting in that
+        # it's different from how most operating system tools act (eg. rm) when
+        # recursive filsystem deletes fail.
+        #
+
+        uri_path = urllib.unquote(urlsplit(uri)[2])
+        if uri_path[-1] == "/":
+            uri_path = uri_path[:-1]
+
+        log.msg("Deleting directory %s" % (filepath.path,))
+
+        # NOTE: len(uri_path) is wrong if os.sep is not one byte long... meh.
+        request_basename = filepath.path[:-len(uri_path)]
+        request_basename_len = len(request_basename)
+
+        errors = ResponseQueue(request_basename, "DELETE", responsecode.NO_CONTENT)
+
+        # FIXME: defer this
+        for dir, subdirs, files in os.walk(filepath.path, topdown=False):
+            for filename in files:
+                path = os.path.join(dir, filename)
+                try:
+                    os.remove(path)
+                except:
+                    errors.add(path, Failure())
+
+            for subdir in subdirs:
+                path = os.path.join(dir, subdir)
+                if os.path.islink(path):
+                    try:
+                        os.remove(path)
+                    except:
+                        errors.add(path, Failure())
+                else:
+                    try:
+                        os.rmdir(path)
+                    except:
+                        errors.add(path, Failure())
+
+        try:
+            os.rmdir(filepath.path)
+        except:
+            raise HTTPError(statusForFailure(
+                Failure(),
+                "deleting directory: %s" % (filepath.path,)
+            ))
+
+        response = errors.response()
+
+    else:
+        #
+        # Delete a file; much simpler, eh?
+        #
+        log.msg("Deleting file %s" % (filepath.path,))
+        try:
+            os.remove(filepath.path)
+        except:
+            raise HTTPError(statusForFailure(
+                Failure(),
+                "deleting file: %s" % (filepath.path,)
+            ))
+
+        response = responsecode.NO_CONTENT
+
+    # Remove stat info for filepath since we deleted the backing file
+    filepath.changed()
+
+    return succeed(response)
+
+def copy(source_filepath, destination_filepath, destination_uri, depth):
+    """
+    Perform a X{COPY} from the given source and destination filepaths.
+    This will perform a X{DELETE} on the destination if necessary; the caller
+    should check and handle the X{overwrite} header before calling L{copy} (as
+    in L{COPYMOVE.prepareForCopy}).
+    @param source_filepath: a L{FilePath} for the file to copy from.
+    @param destination_filepath: a L{FilePath} for the file to copy to.
+    @param destination_uri: the URI of the destination resource.
+    @param depth: the recursion X{Depth} for the X{COPY} operation, which must
+        be one of "0", "1", or "infinity".
+    @raise HTTPError: (containing a response with a status code of
+        L{responsecode.BAD_REQUEST}) if C{depth} is not "0", "1" or "infinity".
+    @raise HTTPError: (containing an appropriate response) if the operation
+        fails.  If C{source_filepath} is a directory, the response will be a
+        L{MultiStatusResponse}.
+    @return: a deferred response with a status code of L{responsecode.CREATED}
+        if the destination already exists, or L{responsecode.NO_CONTENT} if the
+        destination was created by the X{COPY} operation.
+    """
+    if source_filepath.isfile():
+        #
+        # Copy the file
+        #
+        log.msg("Copying file %s to %s" % (source_filepath.path, destination_filepath.path))
+
+        try:
+            source_file = source_filepath.open()
+        except:
+            raise HTTPError(statusForFailure(
+                Failure(),
+                "opening file for reading: %s" % (source_filepath.path,)
+            ))
+    
+        source_stream = FileStream(source_file)
+        response = waitForDeferred(put(source_stream, destination_filepath, destination_uri))
+        yield response
+        try:
+            response = response.getResult()
+        finally:
+            source_stream.close()
+            source_file.close()
+        checkResponse(response, "put", responsecode.NO_CONTENT, responsecode.CREATED)
+        yield response
+        return
+
+    elif source_filepath.isdir():
+        if destination_filepath.exists():
+            #
+            # Delete the destination
+            #
+            response = waitForDeferred(delete(destination_uri, destination_filepath))
+            yield response
+            response = response.getResult()
+            checkResponse(response, "delete", responsecode.NO_CONTENT)
+            success_code = responsecode.NO_CONTENT
+        else:
+            success_code = responsecode.CREATED
+
+        #
+        # Copy the directory
+        #
+        log.msg("Copying directory %s to %s" % (source_filepath.path, destination_filepath.path))
+
+        source_basename = source_filepath.path
+        destination_basename = destination_filepath.path
+
+        errors = ResponseQueue(source_basename, "COPY", success_code)
+
+        if destination_filepath.parent().isdir():
+            if os.path.islink(source_basename):
+                link_destination = os.readlink(source_basename)
+                if link_destination[0] != os.path.sep:
+                    link_destination = os.path.join(source_basename, link_destination)
+                try:
+                    os.symlink(destination_basename, link_destination)
+                except:
+                    errors.add(source_basename, Failure())
+            else:
+                try:
+                    os.mkdir(destination_basename)
+                except:
+                    raise HTTPError(statusForFailure(
+                        Failure(),
+                        "creating directory %s" % (destination_basename,)
+                    ))
+
+                if depth == "0": 
+                    yield success_code
+                    return
+        else:
+            raise HTTPError(StatusResponse(
+                responsecode.CONFLICT,
+                "Parent collection for destination %s does not exist" % (destination_uri,)
+            ))
+
+        #
+        # Recursive copy
+        #
+        # FIXME: When we report errors, do we report them on the source URI
+        # or on the destination URI?  We're using the source URI here.
+        #
+        # FIXME: defer the walk?
+
+        source_basename_len = len(source_basename)
+
+        def paths(basepath, subpath):
+            source_path = os.path.join(basepath, subpath)
+            assert source_path.startswith(source_basename)
+            destination_path = os.path.join(destination_basename, source_path[source_basename_len+1:])
+            return source_path, destination_path
+
+        for dir, subdirs, files in os.walk(source_filepath.path, topdown=True):
+            for filename in files:
+                source_path, destination_path = paths(dir, filename)
+                if not os.path.isdir(os.path.dirname(destination_path)):
+                    errors.add(source_path, responsecode.NOT_FOUND)
+                else:
+                    response = waitForDeferred(copy(FilePath(source_path), FilePath(destination_path), destination_uri, depth))
+                    yield response
+                    response = response.getResult()
+                    checkResponse(response, "copy", responsecode.CREATED, responsecode.NO_CONTENT)
+
+            for subdir in subdirs:
+                source_path, destination_path = paths(dir, subdir)
+                log.msg("Copying directory %s to %s" % (source_path, destination_path))
+
+                if not os.path.isdir(os.path.dirname(destination_path)):
+                    errors.add(source_path, responsecode.CONFLICT)
+                else:
+                    if os.path.islink(source_path):
+                        link_destination = os.readlink(source_path)
+                        if link_destination[0] != os.path.sep:
+                            link_destination = os.path.join(source_path, link_destination)
+                        try:
+                            os.symlink(destination_path, link_destination)
+                        except:
+                            errors.add(source_path, Failure())
+                    else:
+                        try:
+                            os.mkdir(destination_path)
+                        except:
+                            errors.add(source_path, Failure())
+
+        yield errors.response()
+        return
+    else:
+        log.err("Unable to COPY to non-file: %s" % (source_filepath.path,))
+        raise HTTPError(StatusResponse(
+            responsecode.FORBIDDEN,
+            "The requested resource exists but is not backed by a regular file."
+        ))
+
+copy = deferredGenerator(copy)
+
+def move(source_filepath, source_uri, destination_filepath, destination_uri, depth):
+    """
+    Perform a X{MOVE} from the given source and destination filepaths.
+    This will perform a X{DELETE} on the destination if necessary; the caller
+    should check and handle the X{overwrite} header before calling L{copy} (as
+    in L{COPYMOVE.prepareForCopy}).
+    Following the X{DELETE}, this will attempt an atomic filesystem move.  If
+    that fails, a X{COPY} operation followed by a X{DELETE} on the source will
+    be attempted instead.
+    @param source_filepath: a L{FilePath} for the file to copy from.
+    @param destination_filepath: a L{FilePath} for the file to copy to.
+    @param destination_uri: the URI of the destination resource.
+    @param depth: the recursion X{Depth} for the X{MOVE} operation, which must
+        be "infinity".
+    @raise HTTPError: (containing a response with a status code of
+        L{responsecode.BAD_REQUEST}) if C{depth} is not "infinity".
+    @raise HTTPError: (containing an appropriate response) if the operation
+        fails.  If C{source_filepath} is a directory, the response will be a
+        L{MultiStatusResponse}.
+    @return: a deferred response with a status code of L{responsecode.CREATED}
+        if the destination already exists, or L{responsecode.NO_CONTENT} if the
+        destination was created by the X{MOVE} operation.
+    """
+    log.msg("Moving %s to %s" % (source_filepath.path, destination_filepath.path))
+
+    #
+    # Choose a success status
+    #
+    if destination_filepath.exists():
+        #
+        # Delete the destination
+        #
+        response = waitForDeferred(delete(destination_uri, destination_filepath))
+        yield response
+        response = response.getResult()
+        checkResponse(response, "delete", responsecode.NO_CONTENT)
+
+        success_code = responsecode.NO_CONTENT
+    else:
+        success_code = responsecode.CREATED
+
+    #
+    # See if rename (which is atomic, and fast) works
+    #
+    try:
+        os.rename(source_filepath.path, destination_filepath.path)
+    except OSError:
+        pass
+    else:
+        # Remove stat info from source filepath since we moved it
+        source_filepath.changed()
+        yield success_code
+        return
+
+    #
+    # Do a copy, then delete the source
+    #
+
+    response = waitForDeferred(copy(source_filepath, destination_filepath, destination_uri, depth))
+    yield response
+    response = response.getResult()
+    checkResponse(response, "copy", responsecode.CREATED, responsecode.NO_CONTENT)
+
+    response = waitForDeferred(delete(source_uri, source_filepath))
+    yield response
+    response = response.getResult()
+    checkResponse(response, "delete", responsecode.NO_CONTENT)
+
+    yield success_code
+
+move = deferredGenerator(move)
+
+def put(stream, filepath, uri=None):
+    """
+    Perform a PUT of the given data stream into the given filepath.
+    @param stream: the stream to write to the destination.
+    @param filepath: the L{FilePath} of the destination file.
+    @param uri: the URI of the destination resource.
+        If the destination exists, if C{uri} is not C{None}, perform a
+        X{DELETE} operation on the destination, but if C{uri} is C{None},
+        delete the destination directly.
+        Note that whether a L{put} deletes the destination directly vs.
+        performing a X{DELETE} on the destination affects the response returned
+        in the event of an error during deletion.  Specifically, X{DELETE}
+        on collections must return a L{MultiStatusResponse} under certain
+        circumstances, whereas X{PUT} isn't required to do so.  Therefore,
+        if the caller expects X{DELETE} semantics, it must provide a valid
+        C{uri}.
+    @raise HTTPError: (containing an appropriate response) if the operation
+        fails.
+    @return: a deferred response with a status code of L{responsecode.CREATED}
+        if the destination already exists, or L{responsecode.NO_CONTENT} if the
+        destination was created by the X{PUT} operation.
+    """
+    log.msg("Writing to file %s" % (filepath.path,))
+
+    if filepath.exists():
+        if uri is None:
+            try:
+                if filepath.isdir():
+                    rmdir(filepath.path)
+                else:
+                    os.remove(filepath.path)
+            except:
+                raise HTTPError(statusForFailure(
+                    Failure(),
+                    "writing to file: %s" % (filepath.path,)
+                ))
+        else:
+            response = waitForDeferred(delete(uri, filepath))
+            yield response
+            response = response.getResult()
+            checkResponse(response, "delete", responsecode.NO_CONTENT)
+
+        success_code = responsecode.NO_CONTENT
+    else:
+        success_code = responsecode.CREATED
+
+    #
+    # Write the contents of the request stream to resource's file
+    #
+
+    try:
+        resource_file = filepath.open("w")
+    except:
+        raise HTTPError(statusForFailure(
+            Failure(),
+            "opening file for writing: %s" % (filepath.path,)
+        ))
+
+    try:
+        x = waitForDeferred(readIntoFile(stream, resource_file))
+        yield x
+        x.getResult()
+    except:
+        raise HTTPError(statusForFailure(
+            Failure(),
+            "writing to file: %s" % (filepath.path,)
+        ))
+
+    # Remove stat info from filepath since we modified the backing file
+    filepath.changed()
+    yield success_code
+
+put = deferredGenerator(put)
+
+def mkcollection(filepath):
+    """
+    Perform a X{MKCOL} on the given filepath.
+    @param filepath: the L{FilePath} of the collection resource to create.
+    @raise HTTPError: (containing an appropriate response) if the operation
+        fails.
+    @return: a deferred response with a status code of L{responsecode.CREATED}
+        if the destination already exists, or L{responsecode.NO_CONTENT} if the
+        destination was created by the X{MKCOL} operation.
+    """
+    try:
+        os.mkdir(filepath.path)
+        # Remove stat info from filepath because we modified it
+        filepath.changed()
+    except:
+        raise HTTPError(statusForFailure(
+            Failure(),
+            "creating directory in MKCOL: %s" % (filepath.path,)
+        ))
+
+    return succeed(responsecode.CREATED)
+
+def rmdir(dirname):
+    """
+    Removes the directory with the given name, as well as its contents.
+    @param dirname: the path to the directory to remove.
+    """
+    for dir, subdirs, files in os.walk(dirname, topdown=False):
+        for filename in files:
+            os.remove(os.path.join(dir, filename))
+        for subdir in subdirs:
+            path = os.path.join(dir, subdir)
+            if os.path.islink(path):
+                os.remove(path)
+            else:
+                os.rmdir(path)
+
+    os.rmdir(dirname)
+
+def checkResponse(response, method, *codes):
+    assert  response in codes, \
+        "%s() returned %r, but should have returned one of %r instead" % (method, response, codes)

Copied: CalendarServer/trunk/twext/web2/dav/http.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/http.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/http.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/http.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,300 @@
+##
+# 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
+##
+
+"""
+HTTP Utilities
+"""
+
+__all__ = [
+    "ErrorResponse",
+    "NeedPrivilegesResponse",
+    "MultiStatusResponse",
+    "ResponseQueue",
+    "PropertyStatusResponseQueue",
+    "statusForFailure",
+    "errorForFailure",
+    "messageForFailure",
+]
+
+import errno
+
+from twisted.python import log
+from twisted.python.failure import Failure
+from twisted.python.filepath import InsecurePath
+from twext.web2 import responsecode
+from twext.web2.iweb import IResponse
+from twext.web2.http import Response, HTTPError, StatusResponse
+from twext.web2.http_headers import MimeType
+from twext.web2.dav import davxml
+from twext.web2.dav.util import joinURL
+
+from twext.web2.dav._errorbase import ErrorResponse
+
+class NeedPrivilegesResponse (ErrorResponse):
+    def __init__(self, base_uri, errors):
+        """
+        An error response which is due to unsufficient privileges, as
+        determined by L{DAVResource.checkPrivileges}.
+        @param base_uri: the base URI for the resources with errors (the URI of
+            the resource on which C{checkPrivileges} was called).
+        @param errors: a sequence of tuples, as returned by
+            C{checkPrivileges}.
+        """
+        denials = []
+
+        for subpath, privileges in errors:
+            if subpath is None:
+                uri = base_uri
+            else:
+                uri = joinURL(base_uri, subpath)
+
+            for p in privileges:
+                denials.append(davxml.Resource(davxml.HRef(uri), 
+                                               davxml.Privilege(p)))
+
+        super(NeedPrivilegesResponse, self).__init__(responsecode.FORBIDDEN, davxml.NeedPrivileges(*denials))
+
+class MultiStatusResponse (Response):
+    """
+    Multi-status L{Response} object.
+    Renders itself as a DAV:multi-status XML document.
+    """
+    def __init__(self, xml_responses):
+        """
+        @param xml_responses: an interable of davxml.Response objects.
+        """
+        Response.__init__(self, code=responsecode.MULTI_STATUS,
+                          stream=davxml.MultiStatus(*xml_responses).toxml())
+
+        self.headers.setHeader("content-type", MimeType("text", "xml"))
+
+class ResponseQueue (object):
+    """
+    Stores a list of (typically error) responses for use in a
+    L{MultiStatusResponse}.
+    """
+    def __init__(self, path_basename, method, success_response):
+        """
+        @param path_basename: the base path for all responses to be added to the 
+            queue.
+            All paths for responses added to the queue must start with
+            C{path_basename}, which will be stripped from the beginning of each
+            path to determine the response's URI.
+        @param method: the name of the method generating the queue.
+        @param success_response: the response to return in lieu of a
+            L{MultiStatusResponse} if no responses are added to this queue.
+        """
+        self.responses         = []
+        self.path_basename     = path_basename
+        self.path_basename_len = len(path_basename)
+        self.method            = method
+        self.success_response  = success_response
+
+    def add(self, path, what):
+        """
+        Add a response.
+        @param path: a path, which must be a subpath of C{path_basename} as
+            provided to L{__init__}.
+        @param what: a status code or a L{Failure} for the given path.
+        """
+        assert path.startswith(self.path_basename), "%s does not start with %s" % (path, self.path_basename)
+
+        if type(what) is int:
+            code    = what
+            error   = None
+            message = responsecode.RESPONSES[code]
+        elif isinstance(what, Failure):
+            code    = statusForFailure(what)
+            error   = errorForFailure(what)
+            message = messageForFailure(what)
+        else:
+            raise AssertionError("Unknown data type: %r" % (what,))
+
+        if code > 400: # Error codes only
+            log.err("Error during %s for %s: %s" % (self.method, path, message))
+
+        uri = path[self.path_basename_len:]
+
+        children = []
+        children.append(davxml.HRef(uri))
+        children.append(davxml.Status.fromResponseCode(code))
+        if error is not None:
+            children.append(error)
+        if message is not None:
+            children.append(davxml.ResponseDescription(message))
+        self.responses.append(davxml.StatusResponse(*children))
+
+    def response(self):
+        """
+        Generate a L{MultiStatusResponse} with the responses contained in the
+        queue or, if no such responses, return the C{success_response} provided
+        to L{__init__}.
+        @return: the response.
+        """
+        if self.responses:
+            return MultiStatusResponse(self.responses)
+        else:
+            return self.success_response
+
+class PropertyStatusResponseQueue (object):
+    """
+    Stores a list of propstat elements for use in a L{Response}
+    in a L{MultiStatusResponse}.
+    """
+    def __init__(self, method, uri, success_response):
+        """
+        @param method: the name of the method generating the queue.
+        @param uri: the URI for the response.
+        @param success_response: the status to return if no
+            L{PropertyStatus} are added to this queue.
+        """
+        self.method            = method
+        self.uri               = uri
+        self.propstats         = []
+        self.success_response  = success_response
+
+    def add(self, what, property):
+        """
+        Add a response.
+        @param what: a status code or a L{Failure} for the given path.
+        @param property: the property whose status is being reported.
+        """
+        if type(what) is int:
+            code    = what
+            error   = None
+            message = responsecode.RESPONSES[code]
+        elif isinstance(what, Failure):
+            code    = statusForFailure(what)
+            error   = errorForFailure(what)
+            message = messageForFailure(what)
+        else:
+            raise AssertionError("Unknown data type: %r" % (what,))
+
+        if len(property.children) > 0:
+            # Re-instantiate as empty element.
+            property = property.emptyCopy()
+
+        if code > 400: # Error codes only
+            log.err("Error during %s for %s: %s" % (self.method, property, message))
+
+        children = []
+        children.append(davxml.PropertyContainer(property))
+        children.append(davxml.Status.fromResponseCode(code))
+        if error is not None:
+            children.append(error)
+        if message is not None:
+            children.append(davxml.ResponseDescription(message))
+        self.propstats.append(davxml.PropertyStatus(*children))
+
+    def error(self):
+        """
+        Convert any 2xx codes in the propstat responses to 424 Failed Dependency.
+        """
+        for index, propstat in enumerate(self.propstats):
+            # Check the status
+            changed_status = False
+            newchildren = []
+            for child in propstat.children:
+                if isinstance(child, davxml.Status) and (child.code / 100 == 2):
+                    # Change the code
+                    newchildren.append(davxml.Status.fromResponseCode(responsecode.FAILED_DEPENDENCY))
+                    changed_status = True
+                elif changed_status and isinstance(child, davxml.ResponseDescription):
+                    newchildren.append(davxml.ResponseDescription(responsecode.RESPONSES[responsecode.FAILED_DEPENDENCY]))
+                else:
+                    newchildren.append(child)
+            self.propstats[index] = davxml.PropertyStatus(*newchildren)
+
+    def response(self):
+        """
+        Generate a response from the responses contained in the queue or, if
+        there are no such responses, return the C{success_response} provided to
+        L{__init__}.
+        @return: a L{davxml.PropertyStatusResponse}.
+        """
+        if self.propstats:
+            return davxml.PropertyStatusResponse(
+                davxml.HRef(self.uri),
+                *self.propstats
+            )
+        else:
+            return davxml.StatusResponse(
+                davxml.HRef(self.uri),
+                davxml.Status.fromResponseCode(self.success_response)
+            )
+
+##
+# Exceptions and response codes
+##
+
+def statusForFailure(failure, what=None):
+    """
+    @param failure: a L{Failure}.
+    @param what: a decription of what was going on when the failure occurred.
+        If what is not C{None}, emit a cooresponding message via L{log.err}.
+    @return: a response code cooresponding to the given C{failure}.
+    """
+    def msg(err):
+        if what is not None:
+            log.msg("%s while %s" % (err, what))
+
+    if failure.check(IOError, OSError):
+        e = failure.value[0]
+        if e == errno.EACCES or e == errno.EPERM:
+            msg("Permission denied")
+            return responsecode.FORBIDDEN
+        elif e == errno.ENOSPC:
+            msg("Out of storage space")
+            return responsecode.INSUFFICIENT_STORAGE_SPACE
+        elif e == errno.ENOENT:
+            msg("Not found")
+            return responsecode.NOT_FOUND
+        else:
+            failure.raiseException()
+    elif failure.check(NotImplementedError):
+        msg("Unimplemented error")
+        return responsecode.NOT_IMPLEMENTED
+    elif failure.check(InsecurePath):
+        msg("Insecure path")
+        return responsecode.FORBIDDEN
+    elif failure.check(HTTPError):
+        code = IResponse(failure.value.response).code
+        msg("%d response" % (code,))
+        return code
+    else:
+        failure.raiseException()
+
+def errorForFailure(failure):
+    if failure.check(HTTPError) and isinstance(failure.value.response, ErrorResponse):
+        return davxml.Error(failure.value.response.error)
+    else:
+        return None
+
+def messageForFailure(failure):
+    if failure.check(HTTPError):
+        if isinstance(failure.value.response, ErrorResponse):
+            return None
+        if isinstance(failure.value.response, StatusResponse):
+            return failure.value.response.description
+    return str(failure)

Copied: CalendarServer/trunk/twext/web2/dav/idav.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/idav.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/idav.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/idav.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,334 @@
+##
+# 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
+##
+
+"""
+web2.dav interfaces.
+"""
+
+__all__ = [ "IDAVResource", "IDAVPrincipalResource", "IDAVPrincipalCollectionResource", ]
+
+from twext.web2.iweb import IResource
+
+class IDAVResource(IResource):
+    """
+    WebDAV resource.
+    """
+    def isCollection():
+        """
+        Checks whether this resource is a collection resource.
+        @return: C{True} if this resource is a collection resource, C{False}
+            otherwise.
+        """
+
+    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
+        as tuples C{(resource, uri)}, where C{resource} is the child resource
+        and C{uri} is a URL path relative to this resource.
+        @param depth: the search depth (one of C{"0"}, C{"1"}, or C{"infinity"})
+        @param request: The current L{IRequest} responsible for this call.
+        @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
+        """
+
+    def hasProperty(property, request):
+        """
+        Checks whether the given property is defined on this resource.
+        @param property: an empty L{davxml.WebDAVElement} instance or a qname
+            tuple.
+        @param request: the request being processed.
+        @return: a deferred value of C{True} if the given property is set on
+            this resource, or C{False} otherwise.
+        """
+
+    def readProperty(property, request):
+        """
+        Reads the given property on this resource.
+        @param property: an empty L{davxml.WebDAVElement} class or instance, or
+            a qname tuple.
+        @param request: the request being processed.
+        @return: a deferred L{davxml.WebDAVElement} instance
+            containing the value of the given property.
+        @raise HTTPError: (containing a response with a status code of
+            L{responsecode.CONFLICT}) if C{property} is not set on this
+            resource.
+        """
+
+    def writeProperty(property, request):
+        """
+        Writes the given property on this resource.
+        @param property: a L{davxml.WebDAVElement} instance.
+        @param request: the request being processed.
+        @return: an empty deferred which fires when the operation is completed.
+        @raise HTTPError: (containing a response with a status code of
+            L{responsecode.CONFLICT}) if C{property} is a read-only property.
+        """
+
+    def removeProperty(property, request):
+        """
+        Removes the given property from this resource.
+        @param property: a L{davxml.WebDAVElement} instance or a qname tuple.
+        @param request: the request being processed.
+        @return: an empty deferred which fires when the operation is completed.
+        @raise HTTPError: (containing a response with a status code of
+            L{responsecode.CONFLICT}) if C{property} is a read-only property or
+            if the property does not exist.
+        """
+
+    def listProperties(request):
+        """
+        @param request: the request being processed.
+        @return: a deferred iterable of qnames for all properties
+            defined for this resource.
+        """
+
+    def supportedReports():
+        """
+        @return: an iterable of L{davxml.Report} elements for each report
+            supported by this resource.
+        """
+
+    def authorize(request, privileges, recurse=False):
+        """
+        Verify that the given request is authorized to perform actions that
+        require the given privileges.
+
+        @param request: the request being processed.
+
+        @param privileges: an iterable of L{davxml.WebDAVElement} elements
+            denoting access control privileges.
+
+        @param recurse: C{True} if a recursive check on all child
+            resources of this resource should be performed as well,
+            C{False} otherwise.
+
+        @return: a Deferred which fires with C{None} when authorization is
+            complete, or errbacks with L{HTTPError} (containing a response with
+            a status code of L{responsecode.UNAUTHORIZED}) if not authorized.
+        """
+
+    def principalCollections():
+        """
+        @return: an interable of L{IDAVPrincipalCollectionResource}s which
+            contain principals used in ACLs for this resource.
+        """
+
+    def setAccessControlList(acl):
+        """
+        Sets the access control list containing the access control list for
+        this resource.
+        @param acl: an L{davxml.ACL} element.
+        """
+
+    def supportedPrivileges(request):
+        """
+        @param request: the request being processed.
+        @return: a L{Deferred} with an L{davxml.SupportedPrivilegeSet} result describing
+            the access control privileges which are supported by this resource.
+        """
+
+    def currentPrivileges(request):
+        """
+        @param request: the request being processed.
+        @return: a sequence of the access control privileges which are
+            set for the currently authenticated user.
+        """
+
+    def accessControlList(request, inheritance=True, expanding=False):
+        """
+        Obtains the access control list for this resource.
+        @param request: the request being processed.
+        @param inheritance: if True, replace inherited privileges with those
+            from the import resource being inherited from, if False just return
+            whatever is set in this ACL.
+        @param expanding: if C{True}, method is called during parent inheritance
+            expansion, if C{False} then not doing parent expansion.
+        @return: a deferred L{davxml.ACL} element containing the
+            access control list for this resource.
+        """
+
+    def privilegesForPrincipal(principal, request):
+        """
+        Evaluate the set of privileges that apply to the specified principal.
+        This involves examing all ace's and granting/denying as appropriate for
+        the specified principal's membership of the ace's prinicpal.
+        @param request: the request being processed.
+        @return: a list of L{Privilege}s that are allowed on this resource for
+            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)
+    """
+
+    def alternateURIs():
+        """
+        Provides the URIs of network resources with additional descriptive
+        information about the principal, for example, a URI to an LDAP record.
+        (RFC 3744, section 4.1)
+        @return: a iterable of URIs.
+        """
+
+
+    def principalURL():
+        """
+        Provides the URL which must be used to identify this principal in ACL
+        requests.  (RFC 3744, section 4.2)
+        @return: a URL.
+        """
+
+
+    def groupMembers():
+        """
+        Provides the principal URLs of principals that are direct members of
+        this (group) principal.  (RFC 3744, section 4.3)
+        @return: a deferred returning an iterable of principal URLs.
+        """
+
+
+    def expandedGroupMembers():
+        """
+        Provides the principal URLs of principals that are members of this
+        (group) principal, as well as members of any group principal which are
+        members of this one.
+
+        @return: a L{Deferred} that fires with 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 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.
+        """
+
+
+    def principalForUser(user):
+        """
+        Retrieve the principal for a given username.
+
+        @param user: the (short) name of a user.
+        @type user: C{str}
+
+        @return: the resource representing the DAV principal resource for the
+            given username.
+
+        @rtype: L{IDAVPrincipalResource}
+        """
+

Deleted: CalendarServer/trunk/twext/web2/dav/method/__init__.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/__init__.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,50 +0,0 @@
-##
-# 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
-##
-
-"""
-WebDAV methods.
-
-Modules in this package provide the implementation of
-twext.web2.dav.static.DAVFile's dispatched methods.
-"""
-
-__all__ = [
-    "acl",
-    "copymove",
-    "delete",
-    "get",
-    "lock",
-    "mkcol",
-    "propfind",
-    "proppatch",
-    "prop_common",
-    "put",
-    "put_common",
-    "report",
-    "report_acl_principal_prop_set",
-    "report_expand",
-    "report_principal_match",
-    "report_principal_property_search",
-    "report_principal_search_property_set",
-]

Copied: CalendarServer/trunk/twext/web2/dav/method/__init__.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/__init__.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/__init__.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,50 @@
+##
+# 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
+##
+
+"""
+WebDAV methods.
+
+Modules in this package provide the implementation of
+twext.web2.dav.static.DAVFile's dispatched methods.
+"""
+
+__all__ = [
+    "acl",
+    "copymove",
+    "delete",
+    "get",
+    "lock",
+    "mkcol",
+    "propfind",
+    "proppatch",
+    "prop_common",
+    "put",
+    "put_common",
+    "report",
+    "report_acl_principal_prop_set",
+    "report_expand",
+    "report_principal_match",
+    "report_principal_property_search",
+    "report_principal_search_property_set",
+]

Deleted: CalendarServer/trunk/twext/web2/dav/method/acl.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/acl.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/acl.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,100 +0,0 @@
-# -*- test-case-name: twext.web2.dav.test.test_lock -*-
-##
-# 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
-##
-
-"""
-WebDAV ACL method
-"""
-
-__all__ = ["http_ACL"]
-
-from twisted.python import log
-from twisted.internet.defer import deferredGenerator, waitForDeferred
-from twext.web2 import responsecode
-from twext.web2.http import StatusResponse, HTTPError
-from twext.web2.dav import davxml
-from twext.web2.dav.http import ErrorResponse
-from twext.web2.dav.util import davXMLFromStream
-
-def http_ACL(self, request):
-    """
-    Respond to a ACL request. (RFC 3744, section 8.1)
-    """
-    if not self.fp.exists():
-        log.err("File not found: %s" % (self.fp.path,))
-        yield responsecode.NOT_FOUND
-        return
-
-    #
-    # Check authentication and access controls
-    #
-    x = waitForDeferred(self.authorize(request, (davxml.WriteACL(),)))
-    yield x
-    x.getResult()
-
-    #
-    # Read request body
-    #
-    doc = waitForDeferred(davXMLFromStream(request.stream))
-    yield doc
-    try:
-        doc = doc.getResult()
-    except ValueError, e:
-        log.err("Error while handling ACL body: %s" % (e,))
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
-
-    #
-    # Set properties
-    #
-    if doc is None:
-        error = "Request XML body is required."
-        log.err(error)
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
-
-    #
-    # Parse request
-    #
-    acl = doc.root_element
-    if not isinstance(acl, davxml.ACL):
-        error = ("Request XML body must be an acl element."
-                 % (davxml.PropertyUpdate.sname(),))
-        log.err(error)
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
-
-    #
-    # Do ACL merger
-    #
-    result = waitForDeferred(self.mergeAccessControlList(acl, request))
-    yield result
-    result = result.getResult()
-
-    #
-    # Return response
-    #
-    if result is None:
-        yield responsecode.OK
-    else:
-        yield ErrorResponse(responsecode.FORBIDDEN, result)
-
-http_ACL = deferredGenerator(http_ACL)

Copied: CalendarServer/trunk/twext/web2/dav/method/acl.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/acl.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/acl.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/acl.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,100 @@
+# -*- test-case-name: twext.web2.dav.test.test_lock -*-
+##
+# 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
+##
+
+"""
+WebDAV ACL method
+"""
+
+__all__ = ["http_ACL"]
+
+from twisted.python import log
+from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twext.web2 import responsecode
+from twext.web2.http import StatusResponse, HTTPError
+from twext.web2.dav import davxml
+from twext.web2.dav.http import ErrorResponse
+from twext.web2.dav.util import davXMLFromStream
+
+def http_ACL(self, request):
+    """
+    Respond to a ACL request. (RFC 3744, section 8.1)
+    """
+    if not self.fp.exists():
+        log.err("File not found: %s" % (self.fp.path,))
+        yield responsecode.NOT_FOUND
+        return
+
+    #
+    # Check authentication and access controls
+    #
+    x = waitForDeferred(self.authorize(request, (davxml.WriteACL(),)))
+    yield x
+    x.getResult()
+
+    #
+    # Read request body
+    #
+    doc = waitForDeferred(davXMLFromStream(request.stream))
+    yield doc
+    try:
+        doc = doc.getResult()
+    except ValueError, e:
+        log.err("Error while handling ACL body: %s" % (e,))
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
+
+    #
+    # Set properties
+    #
+    if doc is None:
+        error = "Request XML body is required."
+        log.err(error)
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
+
+    #
+    # Parse request
+    #
+    acl = doc.root_element
+    if not isinstance(acl, davxml.ACL):
+        error = ("Request XML body must be an acl element."
+                 % (davxml.PropertyUpdate.sname(),))
+        log.err(error)
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
+
+    #
+    # Do ACL merger
+    #
+    result = waitForDeferred(self.mergeAccessControlList(acl, request))
+    yield result
+    result = result.getResult()
+
+    #
+    # Return response
+    #
+    if result is None:
+        yield responsecode.OK
+    else:
+        yield ErrorResponse(responsecode.FORBIDDEN, result)
+
+http_ACL = deferredGenerator(http_ACL)

Deleted: CalendarServer/trunk/twext/web2/dav/method/copymove.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/copymove.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/copymove.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,266 +0,0 @@
-# -*- test-case-name: twext.web2.dav.test.test_copy,twext.web2.dav.test.test_move -*-
-##
-# 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
-##
-
-"""
-WebDAV COPY and MOVE methods.
-"""
-
-__all__ = ["http_COPY", "http_MOVE"]
-
-import urlparse
-
-from twisted.python import log
-from twisted.internet.defer import waitForDeferred, deferredGenerator
-from twext.web2 import responsecode
-from twext.web2.dav.fileop import move
-from twext.web2.http import HTTPError, StatusResponse
-from twext.web2.filter.location import addLocation
-from twext.web2.dav import davxml
-from twext.web2.dav.idav import IDAVResource
-from twext.web2.dav.method import put_common
-from twext.web2.dav.util import parentForURL
-
-# FIXME: This is circular
-import twext.web2.dav.static
-
-def http_COPY(self, request):
-    """
-    Respond to a COPY request. (RFC 2518, section 8.8)
-    """
-    r = waitForDeferred(prepareForCopy(self, request))
-    yield r
-    r = r.getResult()
-
-    destination, destination_uri, depth = r
-
-    #
-    # Check authentication and access controls
-    #
-    x = waitForDeferred(self.authorize(request, (davxml.Read(),), recurse=True))
-    yield x
-    x.getResult()
-
-    if destination.exists():
-        x = waitForDeferred(destination.authorize(
-            request,
-            (davxml.WriteContent(), davxml.WriteProperties()),
-            recurse=True
-        ))
-        yield x
-        x.getResult()
-    else:
-        destparent = waitForDeferred(request.locateResource(parentForURL(destination_uri)))
-        yield destparent
-        destparent = destparent.getResult()
-
-        x = waitForDeferred(destparent.authorize(request, (davxml.Bind(),)))
-        yield x
-        x.getResult()
-
-        # May need to add a location header
-        addLocation(request, destination_uri)
-
-    #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()
-
-http_COPY = deferredGenerator(http_COPY)
-
-def http_MOVE(self, request):
-    """
-    Respond to a MOVE request. (RFC 2518, section 8.9)
-    """
-    r = waitForDeferred(prepareForCopy(self, request))
-    yield r
-    r = r.getResult()
-
-    destination, destination_uri, depth = r
-
-    #
-    # Check authentication and access controls
-    #
-    parentURL = parentForURL(request.uri)
-    parent = waitForDeferred(request.locateResource(parentURL))
-    yield parent
-    parent = parent.getResult()
-
-    x = waitForDeferred(parent.authorize(request, (davxml.Unbind(),)))
-    yield x
-    x.getResult()
-
-    if destination.exists():
-        x = waitForDeferred(destination.authorize(
-            request,
-            (davxml.Bind(), davxml.Unbind()),
-            recurse=True
-        ))
-        yield x
-        x.getResult()
-    else:
-        destparentURL = parentForURL(destination_uri)
-        destparent = waitForDeferred(request.locateResource(destparentURL))
-        yield destparent
-        destparent = destparent.getResult()
-
-        x = waitForDeferred(destparent.authorize(request, (davxml.Bind(),)))
-        yield x
-        x.getResult()
-
-        # May need to add a location header
-        addLocation(request, destination_uri)
-
-    #
-    # RFC 2518, section 8.9 says that we must act as if the Depth header is set
-    # to infinity, and that the client must omit the Depth header or set it to
-    # infinity.
-    #
-    # This seems somewhat at odds with the notion that a bad request should be
-    # rejected outright; if the client sends a bad depth header, the client is
-    # broken, and section 8 suggests that a bad request should be rejected...
-    #
-    # Let's play it safe for now and ignore broken clients.
-    #
-    if self.fp.isdir() and depth != "infinity":
-        msg = "Client sent illegal depth header value for MOVE: %s" % (depth,)
-        log.err(msg)
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
-
-    # 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()
-
-http_MOVE = deferredGenerator(http_MOVE)
-
-def prepareForCopy(self, request):
-    #
-    # Get the depth
-    #
-
-    depth = request.headers.getHeader("depth", "infinity")
-
-    if depth not in ("0", "infinity"):
-        msg = ("Client sent illegal depth header value: %s" % (depth,))
-        log.err(msg)
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
-
-    #
-    # Verify this resource exists
-    #
-
-    if not self.exists():
-        log.err("File not found: %s" % (self.fp.path,))
-        raise HTTPError(StatusResponse(
-            responsecode.NOT_FOUND,
-            "Source resource %s not found." % (request.uri,)
-        ))
-
-    #
-    # Get the destination
-    #
-
-    destination_uri = request.headers.getHeader("destination")
-
-    if not destination_uri:
-        msg = "No destination header in %s request." % (request.method,)
-        log.err(msg)
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
-
-    d = request.locateResource(destination_uri)
-    d.addCallback(_prepareForCopy, destination_uri, request, depth)
-
-    return d
-
-def _prepareForCopy(destination, destination_uri, request, depth):
-    #
-    # Destination must be a DAV resource
-    #
-
-    try:
-        destination = IDAVResource(destination)
-    except TypeError:
-        log.err("Attempt to %s to a non-DAV resource: (%s) %s"
-                % (request.method, destination.__class__, destination_uri))
-        raise HTTPError(StatusResponse(
-            responsecode.FORBIDDEN,
-            "Destination %s is not a WebDAV resource." % (destination_uri,)
-        ))
-
-    #
-    # FIXME: Right now we don't know how to copy to a non-DAVFile resource.
-    # We may need some more API in IDAVResource.
-    # So far, we need: .exists(), .fp.parent()
-    #
-
-    if not isinstance(destination, twext.web2.dav.static.DAVFile):
-        log.err("DAV copy between non-DAVFile DAV resources isn't implemented")
-        raise HTTPError(StatusResponse(
-            responsecode.NOT_IMPLEMENTED,
-            "Destination %s is not a DAVFile resource." % (destination_uri,)
-        ))
-
-    #
-    # Check for existing destination resource
-    #
-
-    overwrite = request.headers.getHeader("overwrite", True)
-
-    if destination.exists() and not overwrite:
-        log.err("Attempt to %s onto existing file without overwrite flag enabled: %s"
-                % (request.method, destination.fp.path))
-        raise HTTPError(StatusResponse(
-            responsecode.PRECONDITION_FAILED,
-            "Destination %s already exists." % (destination_uri,)
-        ))
-
-    #
-    # Make sure destination's parent exists
-    #
-
-    if not destination.fp.parent().isdir():
-        log.err("Attempt to %s to a resource with no parent: %s"
-                % (request.method, destination.fp.path))
-        raise HTTPError(StatusResponse(responsecode.CONFLICT, "No parent collection."))
-
-    return destination, destination_uri, depth

Copied: CalendarServer/trunk/twext/web2/dav/method/copymove.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/copymove.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/copymove.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/copymove.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,266 @@
+# -*- test-case-name: twext.web2.dav.test.test_copy,twext.web2.dav.test.test_move -*-
+##
+# 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
+##
+
+"""
+WebDAV COPY and MOVE methods.
+"""
+
+__all__ = ["http_COPY", "http_MOVE"]
+
+import urlparse
+
+from twisted.python import log
+from twisted.internet.defer import waitForDeferred, deferredGenerator
+from twext.web2 import responsecode
+from twext.web2.dav.fileop import move
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2.filter.location import addLocation
+from twext.web2.dav import davxml
+from twext.web2.dav.idav import IDAVResource
+from twext.web2.dav.method import put_common
+from twext.web2.dav.util import parentForURL
+
+# FIXME: This is circular
+import twext.web2.dav.static
+
+def http_COPY(self, request):
+    """
+    Respond to a COPY request. (RFC 2518, section 8.8)
+    """
+    r = waitForDeferred(prepareForCopy(self, request))
+    yield r
+    r = r.getResult()
+
+    destination, destination_uri, depth = r
+
+    #
+    # Check authentication and access controls
+    #
+    x = waitForDeferred(self.authorize(request, (davxml.Read(),), recurse=True))
+    yield x
+    x.getResult()
+
+    if destination.exists():
+        x = waitForDeferred(destination.authorize(
+            request,
+            (davxml.WriteContent(), davxml.WriteProperties()),
+            recurse=True
+        ))
+        yield x
+        x.getResult()
+    else:
+        destparent = waitForDeferred(request.locateResource(parentForURL(destination_uri)))
+        yield destparent
+        destparent = destparent.getResult()
+
+        x = waitForDeferred(destparent.authorize(request, (davxml.Bind(),)))
+        yield x
+        x.getResult()
+
+        # May need to add a location header
+        addLocation(request, destination_uri)
+
+    #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()
+
+http_COPY = deferredGenerator(http_COPY)
+
+def http_MOVE(self, request):
+    """
+    Respond to a MOVE request. (RFC 2518, section 8.9)
+    """
+    r = waitForDeferred(prepareForCopy(self, request))
+    yield r
+    r = r.getResult()
+
+    destination, destination_uri, depth = r
+
+    #
+    # Check authentication and access controls
+    #
+    parentURL = parentForURL(request.uri)
+    parent = waitForDeferred(request.locateResource(parentURL))
+    yield parent
+    parent = parent.getResult()
+
+    x = waitForDeferred(parent.authorize(request, (davxml.Unbind(),)))
+    yield x
+    x.getResult()
+
+    if destination.exists():
+        x = waitForDeferred(destination.authorize(
+            request,
+            (davxml.Bind(), davxml.Unbind()),
+            recurse=True
+        ))
+        yield x
+        x.getResult()
+    else:
+        destparentURL = parentForURL(destination_uri)
+        destparent = waitForDeferred(request.locateResource(destparentURL))
+        yield destparent
+        destparent = destparent.getResult()
+
+        x = waitForDeferred(destparent.authorize(request, (davxml.Bind(),)))
+        yield x
+        x.getResult()
+
+        # May need to add a location header
+        addLocation(request, destination_uri)
+
+    #
+    # RFC 2518, section 8.9 says that we must act as if the Depth header is set
+    # to infinity, and that the client must omit the Depth header or set it to
+    # infinity.
+    #
+    # This seems somewhat at odds with the notion that a bad request should be
+    # rejected outright; if the client sends a bad depth header, the client is
+    # broken, and section 8 suggests that a bad request should be rejected...
+    #
+    # Let's play it safe for now and ignore broken clients.
+    #
+    if self.fp.isdir() and depth != "infinity":
+        msg = "Client sent illegal depth header value for MOVE: %s" % (depth,)
+        log.err(msg)
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
+
+    # 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()
+
+http_MOVE = deferredGenerator(http_MOVE)
+
+def prepareForCopy(self, request):
+    #
+    # Get the depth
+    #
+
+    depth = request.headers.getHeader("depth", "infinity")
+
+    if depth not in ("0", "infinity"):
+        msg = ("Client sent illegal depth header value: %s" % (depth,))
+        log.err(msg)
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
+
+    #
+    # Verify this resource exists
+    #
+
+    if not self.exists():
+        log.err("File not found: %s" % (self.fp.path,))
+        raise HTTPError(StatusResponse(
+            responsecode.NOT_FOUND,
+            "Source resource %s not found." % (request.uri,)
+        ))
+
+    #
+    # Get the destination
+    #
+
+    destination_uri = request.headers.getHeader("destination")
+
+    if not destination_uri:
+        msg = "No destination header in %s request." % (request.method,)
+        log.err(msg)
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
+
+    d = request.locateResource(destination_uri)
+    d.addCallback(_prepareForCopy, destination_uri, request, depth)
+
+    return d
+
+def _prepareForCopy(destination, destination_uri, request, depth):
+    #
+    # Destination must be a DAV resource
+    #
+
+    try:
+        destination = IDAVResource(destination)
+    except TypeError:
+        log.err("Attempt to %s to a non-DAV resource: (%s) %s"
+                % (request.method, destination.__class__, destination_uri))
+        raise HTTPError(StatusResponse(
+            responsecode.FORBIDDEN,
+            "Destination %s is not a WebDAV resource." % (destination_uri,)
+        ))
+
+    #
+    # FIXME: Right now we don't know how to copy to a non-DAVFile resource.
+    # We may need some more API in IDAVResource.
+    # So far, we need: .exists(), .fp.parent()
+    #
+
+    if not isinstance(destination, twext.web2.dav.static.DAVFile):
+        log.err("DAV copy between non-DAVFile DAV resources isn't implemented")
+        raise HTTPError(StatusResponse(
+            responsecode.NOT_IMPLEMENTED,
+            "Destination %s is not a DAVFile resource." % (destination_uri,)
+        ))
+
+    #
+    # Check for existing destination resource
+    #
+
+    overwrite = request.headers.getHeader("overwrite", True)
+
+    if destination.exists() and not overwrite:
+        log.err("Attempt to %s onto existing file without overwrite flag enabled: %s"
+                % (request.method, destination.fp.path))
+        raise HTTPError(StatusResponse(
+            responsecode.PRECONDITION_FAILED,
+            "Destination %s already exists." % (destination_uri,)
+        ))
+
+    #
+    # Make sure destination's parent exists
+    #
+
+    if not destination.fp.parent().isdir():
+        log.err("Attempt to %s to a resource with no parent: %s"
+                % (request.method, destination.fp.path))
+        raise HTTPError(StatusResponse(responsecode.CONFLICT, "No parent collection."))
+
+    return destination, destination_uri, depth

Deleted: CalendarServer/trunk/twext/web2/dav/method/delete.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/delete.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/delete.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,85 +0,0 @@
-# -*- test-case-name: twext.web2.dav.test.test_delete -*-
-##
-# 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
-##
-
-"""
-WebDAV DELETE method
-"""
-
-__all__ = ["http_DELETE"]
-
-from twisted.python import log
-from twisted.internet.defer import waitForDeferred, deferredGenerator
-from twext.web2 import responsecode
-from twext.web2.http import HTTPError
-from twext.web2.dav import davxml
-from twext.web2.dav.fileop import delete
-from twext.web2.dav.util import parentForURL
-
-def http_DELETE(self, request):
-    """
-    Respond to a DELETE request. (RFC 2518, section 8.6)
-    """
-    if not self.fp.exists():
-        log.err("File not found: %s" % (self.fp.path,))
-        raise HTTPError(responsecode.NOT_FOUND)
-
-    depth = request.headers.getHeader("depth", "infinity")
-
-    #
-    # Check authentication and access controls
-    #
-    parent = waitForDeferred(request.locateResource(parentForURL(request.uri)))
-    yield parent
-    parent = parent.getResult()
-
-    x = waitForDeferred(parent.authorize(request, (davxml.Unbind(),)))
-    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
-    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/twext/web2/dav/method/delete.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/delete.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/delete.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/delete.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,85 @@
+# -*- test-case-name: twext.web2.dav.test.test_delete -*-
+##
+# 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
+##
+
+"""
+WebDAV DELETE method
+"""
+
+__all__ = ["http_DELETE"]
+
+from twisted.python import log
+from twisted.internet.defer import waitForDeferred, deferredGenerator
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError
+from twext.web2.dav import davxml
+from twext.web2.dav.fileop import delete
+from twext.web2.dav.util import parentForURL
+
+def http_DELETE(self, request):
+    """
+    Respond to a DELETE request. (RFC 2518, section 8.6)
+    """
+    if not self.fp.exists():
+        log.err("File not found: %s" % (self.fp.path,))
+        raise HTTPError(responsecode.NOT_FOUND)
+
+    depth = request.headers.getHeader("depth", "infinity")
+
+    #
+    # Check authentication and access controls
+    #
+    parent = waitForDeferred(request.locateResource(parentForURL(request.uri)))
+    yield parent
+    parent = parent.getResult()
+
+    x = waitForDeferred(parent.authorize(request, (davxml.Unbind(),)))
+    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
+    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)

Deleted: CalendarServer/trunk/twext/web2/dav/method/get.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/get.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/get.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,58 +0,0 @@
-# -*- test-case-name: twext.web2.dav.test.test_lock -*-
-##
-# 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
-##
-
-"""
-WebDAV GET and HEAD methods
-"""
-
-__all__ = ["http_OPTIONS", "http_HEAD", "http_GET"]
-
-import twext
-
-from twext.web2.dav import davxml
-from twext.web2.dav.util import parentForURL
-
-def http_OPTIONS(self, request):
-    d = authorize(self, request)
-    d.addCallback(lambda _: super(twext.web2.dav.static.DAVFile, self).http_OPTIONS(request))
-    return d
-
-def http_HEAD(self, request):
-    d = authorize(self, request)
-    d.addCallback(lambda _: super(twext.web2.dav.static.DAVFile, self).http_HEAD(request))
-    return d
-
-def http_GET(self, request):
-    d = authorize(self, request)
-    d.addCallback(lambda _: super(twext.web2.dav.static.DAVFile, self).http_GET(request))
-    return d
-
-def authorize(self, request):
-    if self.exists():
-        d = self.authorize(request, (davxml.Read(),))
-    else:
-        d = request.locateResource(parentForURL(request.uri))
-        d.addCallback(lambda parent: parent.authorize(request, (davxml.Bind(),)))
-    return d

Copied: CalendarServer/trunk/twext/web2/dav/method/get.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/get.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/get.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/get.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,58 @@
+# -*- test-case-name: twext.web2.dav.test.test_lock -*-
+##
+# 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
+##
+
+"""
+WebDAV GET and HEAD methods
+"""
+
+__all__ = ["http_OPTIONS", "http_HEAD", "http_GET"]
+
+import twext
+
+from twext.web2.dav import davxml
+from twext.web2.dav.util import parentForURL
+
+def http_OPTIONS(self, request):
+    d = authorize(self, request)
+    d.addCallback(lambda _: super(twext.web2.dav.static.DAVFile, self).http_OPTIONS(request))
+    return d
+
+def http_HEAD(self, request):
+    d = authorize(self, request)
+    d.addCallback(lambda _: super(twext.web2.dav.static.DAVFile, self).http_HEAD(request))
+    return d
+
+def http_GET(self, request):
+    d = authorize(self, request)
+    d.addCallback(lambda _: super(twext.web2.dav.static.DAVFile, self).http_GET(request))
+    return d
+
+def authorize(self, request):
+    if self.exists():
+        d = self.authorize(request, (davxml.Read(),))
+    else:
+        d = request.locateResource(parentForURL(request.uri))
+        d.addCallback(lambda parent: parent.authorize(request, (davxml.Bind(),)))
+    return d

Deleted: CalendarServer/trunk/twext/web2/dav/method/lock.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/lock.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/lock.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,44 +0,0 @@
-# -*- test-case-name: twext.web2.dav.test.test_lock -*-
-##
-# 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
-##
-
-"""
-WebDAV LOCK and UNLOCK methods
-"""
-
-__all__ = ["http_LOCK", "http_UNLOCK"]
-
-from twext.web2 import responsecode
-
-def http_LOCK(self, request):
-    """
-    Respond to a LOCK request. (RFC 2518, section 8.10)
-    """
-    return responsecode.NOT_IMPLEMENTED
-
-def http_UNLOCK(self, request):
-    """
-    Respond to a UNLOCK request. (RFC 2518, section 8.11)
-    """
-    return responsecode.NOT_IMPLEMENTED

Copied: CalendarServer/trunk/twext/web2/dav/method/lock.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/lock.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/lock.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/lock.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,44 @@
+# -*- test-case-name: twext.web2.dav.test.test_lock -*-
+##
+# 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
+##
+
+"""
+WebDAV LOCK and UNLOCK methods
+"""
+
+__all__ = ["http_LOCK", "http_UNLOCK"]
+
+from twext.web2 import responsecode
+
+def http_LOCK(self, request):
+    """
+    Respond to a LOCK request. (RFC 2518, section 8.10)
+    """
+    return responsecode.NOT_IMPLEMENTED
+
+def http_UNLOCK(self, request):
+    """
+    Respond to a UNLOCK request. (RFC 2518, section 8.11)
+    """
+    return responsecode.NOT_IMPLEMENTED

Deleted: CalendarServer/trunk/twext/web2/dav/method/mkcol.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/mkcol.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/mkcol.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,80 +0,0 @@
-# -*- test-case-name: twext.web2.dav.test.test_mkcol -*-
-##
-# 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
-##
-
-"""
-WebDAV MKCOL method
-"""
-
-__all__ = ["http_MKCOL"]
-
-from twisted.python import log
-from twisted.internet.defer import deferredGenerator, waitForDeferred
-from twext.web2 import responsecode
-from twext.web2.http import HTTPError, StatusResponse
-from twext.web2.dav import davxml
-from twext.web2.dav.fileop import mkcollection
-from twext.web2.dav.util import noDataFromStream, parentForURL
-
-def http_MKCOL(self, request):
-    """
-    Respond to a MKCOL request. (RFC 2518, section 8.3)
-    """
-    parent = waitForDeferred(request.locateResource(parentForURL(request.uri)))
-    yield parent
-    parent = parent.getResult()
-
-    x = waitForDeferred(parent.authorize(request, (davxml.Bind(),)))
-    yield x
-    x.getResult()
-
-    if self.fp.exists():
-        log.err("Attempt to create collection where file exists: %s"
-                % (self.fp.path,))
-        raise HTTPError(responsecode.NOT_ALLOWED)
-
-    if not parent.isCollection():
-        log.err("Attempt to create collection with non-collection parent: %s"
-                % (self.fp.path,))
-        raise HTTPError(StatusResponse(
-            responsecode.CONFLICT,
-            "Parent resource is not a collection."
-        ))
-
-    #
-    # Read request body
-    #
-    x = waitForDeferred(noDataFromStream(request.stream))
-    yield x
-    try:
-        x.getResult()
-    except ValueError, e:
-        log.err("Error while handling MKCOL body: %s" % (e,))
-        raise HTTPError(responsecode.UNSUPPORTED_MEDIA_TYPE)
-
-    response = waitForDeferred(mkcollection(self.fp))
-    yield response
-    yield response.getResult()
-
-http_MKCOL = deferredGenerator(http_MKCOL)

Copied: CalendarServer/trunk/twext/web2/dav/method/mkcol.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/mkcol.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/mkcol.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/mkcol.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,80 @@
+# -*- test-case-name: twext.web2.dav.test.test_mkcol -*-
+##
+# 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
+##
+
+"""
+WebDAV MKCOL method
+"""
+
+__all__ = ["http_MKCOL"]
+
+from twisted.python import log
+from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2.dav import davxml
+from twext.web2.dav.fileop import mkcollection
+from twext.web2.dav.util import noDataFromStream, parentForURL
+
+def http_MKCOL(self, request):
+    """
+    Respond to a MKCOL request. (RFC 2518, section 8.3)
+    """
+    parent = waitForDeferred(request.locateResource(parentForURL(request.uri)))
+    yield parent
+    parent = parent.getResult()
+
+    x = waitForDeferred(parent.authorize(request, (davxml.Bind(),)))
+    yield x
+    x.getResult()
+
+    if self.fp.exists():
+        log.err("Attempt to create collection where file exists: %s"
+                % (self.fp.path,))
+        raise HTTPError(responsecode.NOT_ALLOWED)
+
+    if not parent.isCollection():
+        log.err("Attempt to create collection with non-collection parent: %s"
+                % (self.fp.path,))
+        raise HTTPError(StatusResponse(
+            responsecode.CONFLICT,
+            "Parent resource is not a collection."
+        ))
+
+    #
+    # Read request body
+    #
+    x = waitForDeferred(noDataFromStream(request.stream))
+    yield x
+    try:
+        x.getResult()
+    except ValueError, e:
+        log.err("Error while handling MKCOL body: %s" % (e,))
+        raise HTTPError(responsecode.UNSUPPORTED_MEDIA_TYPE)
+
+    response = waitForDeferred(mkcollection(self.fp))
+    yield response
+    yield response.getResult()
+
+http_MKCOL = deferredGenerator(http_MKCOL)

Deleted: CalendarServer/trunk/twext/web2/dav/method/prop_common.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/prop_common.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/prop_common.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,103 +0,0 @@
-##
-# Cyrus Daboo, cdaboo at apple.com
-# Copyright 2006 Apple Computer, Inc.  All Rights Reserved.
-##
-
-__all__ = [
-    "responseForHref",
-    "propertyListForResource",
-]
-
-from twisted.internet.defer import deferredGenerator, waitForDeferred
-from twisted.python import log
-from twisted.python.failure import Failure
-from twext.web2 import responsecode
-from twext.web2.dav import davxml
-from twext.web2.dav.element.base import WebDAVElement
-from twext.web2.dav.http import statusForFailure
-from twext.web2.dav.method.propfind import propertyName
-
-def responseForHref(request, responses, href, resource, propertiesForResource, propertyreq):
-
-    if propertiesForResource is not None:
-        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:
-                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(
-                href,
-                davxml.Status.fromResponseCode(responsecode.OK),
-            )
-        )
-
-responseForHref = deferredGenerator(responseForHref)
-
-def propertyListForResource(request, prop, resource):
-    """
-    Return the specified properties on the specified resource.
-    @param request: the L{IRequest} for the current request.
-    @param prop: the L{PropertyContainer} element for the properties of interest.
-    @param resource: the L{DAVFile} for the targetted resource.
-    @return: a map of OK and NOT FOUND property values.
-    """
-    
-    return _namedPropertiesForResource(request, prop.children, resource)
-
-def _namedPropertiesForResource(request, props, resource):
-    """
-    Return the specified properties on the specified resource.
-    @param request: the L{IRequest} for the current request.
-    @param props: a list of property elements or qname tuples for the properties of interest.
-    @param resource: the L{DAVFile} for the targetted resource.
-    @return: a map of OK and NOT FOUND property values.
-    """
-    properties_by_status = {
-        responsecode.OK        : [],
-        responsecode.NOT_FOUND : [],
-    }
-    
-    for property in props:
-        if isinstance(property, WebDAVElement):
-            qname = property.qname()
-        else:
-            qname = property
-    
-        props = waitForDeferred(resource.listProperties(request))
-        yield props
-        props = props.getResult()
-        if qname in props:
-            try:
-                prop = waitForDeferred(resource.readProperty(qname, request))
-                yield prop
-                prop = prop.getResult()
-                properties_by_status[responsecode.OK].append(prop)
-            except:
-                f = Failure()
-    
-                log.err("Error reading property %r for resource %s: %s" % (qname, request.uri, f.value))
-    
-                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))
-        else:
-            properties_by_status[responsecode.NOT_FOUND].append(propertyName(qname))
-    
-    yield properties_by_status
-
-_namedPropertiesForResource = deferredGenerator(_namedPropertiesForResource)

Copied: CalendarServer/trunk/twext/web2/dav/method/prop_common.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/prop_common.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/prop_common.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/prop_common.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,103 @@
+##
+# Cyrus Daboo, cdaboo at apple.com
+# Copyright 2006 Apple Computer, Inc.  All Rights Reserved.
+##
+
+__all__ = [
+    "responseForHref",
+    "propertyListForResource",
+]
+
+from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twisted.python import log
+from twisted.python.failure import Failure
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.element.base import WebDAVElement
+from twext.web2.dav.http import statusForFailure
+from twext.web2.dav.method.propfind import propertyName
+
+def responseForHref(request, responses, href, resource, propertiesForResource, propertyreq):
+
+    if propertiesForResource is not None:
+        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:
+                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(
+                href,
+                davxml.Status.fromResponseCode(responsecode.OK),
+            )
+        )
+
+responseForHref = deferredGenerator(responseForHref)
+
+def propertyListForResource(request, prop, resource):
+    """
+    Return the specified properties on the specified resource.
+    @param request: the L{IRequest} for the current request.
+    @param prop: the L{PropertyContainer} element for the properties of interest.
+    @param resource: the L{DAVFile} for the targetted resource.
+    @return: a map of OK and NOT FOUND property values.
+    """
+    
+    return _namedPropertiesForResource(request, prop.children, resource)
+
+def _namedPropertiesForResource(request, props, resource):
+    """
+    Return the specified properties on the specified resource.
+    @param request: the L{IRequest} for the current request.
+    @param props: a list of property elements or qname tuples for the properties of interest.
+    @param resource: the L{DAVFile} for the targetted resource.
+    @return: a map of OK and NOT FOUND property values.
+    """
+    properties_by_status = {
+        responsecode.OK        : [],
+        responsecode.NOT_FOUND : [],
+    }
+    
+    for property in props:
+        if isinstance(property, WebDAVElement):
+            qname = property.qname()
+        else:
+            qname = property
+    
+        props = waitForDeferred(resource.listProperties(request))
+        yield props
+        props = props.getResult()
+        if qname in props:
+            try:
+                prop = waitForDeferred(resource.readProperty(qname, request))
+                yield prop
+                prop = prop.getResult()
+                properties_by_status[responsecode.OK].append(prop)
+            except:
+                f = Failure()
+    
+                log.err("Error reading property %r for resource %s: %s" % (qname, request.uri, f.value))
+    
+                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))
+        else:
+            properties_by_status[responsecode.NOT_FOUND].append(propertyName(qname))
+    
+    yield properties_by_status
+
+_namedPropertiesForResource = deferredGenerator(_namedPropertiesForResource)

Deleted: CalendarServer/trunk/twext/web2/dav/method/propfind.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/propfind.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/propfind.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,209 +0,0 @@
-# -*- test-case-name: twext.web2.dav.test.test_prop.PROP.test_PROPFIND -*-
-##
-# 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
-##
-
-"""
-WebDAV PROPFIND method
-"""
-
-__all__ = [
-    "http_PROPFIND",
-    "propertyName",
-]
-
-from twisted.python import log
-from twisted.python.failure import Failure
-from twisted.internet.defer import deferredGenerator, waitForDeferred
-from twext.web2.http import HTTPError
-from twext.web2 import responsecode
-from twext.web2.http import StatusResponse
-from twext.web2.dav import davxml
-from twext.web2.dav.http import MultiStatusResponse, statusForFailure
-from twext.web2.dav.util import normalizeURL, davXMLFromStream
-
-def http_PROPFIND(self, request):
-    """
-    Respond to a PROPFIND request. (RFC 2518, section 8.1)
-    """
-    if not self.exists():
-        log.err("File not found: %s" % (self.fp.path,))
-        raise HTTPError(responsecode.NOT_FOUND)
-
-    #
-    # Check authentication and access controls
-    #
-    x = waitForDeferred(self.authorize(request, (davxml.Read(),)))
-    yield x
-    x.getResult()
-
-    #
-    # Read request body
-    #
-    try:
-        doc = waitForDeferred(davXMLFromStream(request.stream))
-        yield doc
-        doc = doc.getResult()
-    except ValueError, e:
-        log.err("Error while handling PROPFIND body: %s" % (e,))
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
-
-    if doc is None:
-        # No request body means get all properties.
-        search_properties = "all"
-    else:
-        #
-        # Parse request
-        #
-        find = doc.root_element
-        if not isinstance(find, davxml.PropertyFind):
-            error = ("Non-%s element in PROPFIND request body: %s"
-                     % (davxml.PropertyFind.sname(), find))
-            log.err(error)
-            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
-
-        container = find.children[0]
-
-        if isinstance(container, davxml.AllProperties):
-            # Get all properties
-            search_properties = "all"
-        elif isinstance(container, davxml.PropertyName):
-            # Get names only
-            search_properties = "names"
-        elif isinstance(container, davxml.PropertyContainer):
-            properties = container.children
-            search_properties = [(p.namespace, p.name) for p in properties]
-        else:
-            raise AssertionError("Unexpected element type in %s: %s"
-                                 % (davxml.PropertyFind.sname(), container))
-
-    #
-    # Generate XML output stream
-    #
-    request_uri = request.uri
-    depth = request.headers.getHeader("depth", "infinity")
-
-    xml_responses = []
-
-    # FIXME: take advantage of the new generative properties of findChildren
-
-    my_url = normalizeURL(request_uri)
-    if self.isCollection() and not my_url.endswith("/"):
-        my_url += "/"
-
-    # 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.
-    filtered_aces = waitForDeferred(self.inheritedACEsforChildren(request))
-    yield filtered_aces
-    filtered_aces = filtered_aces.getResult()
-
-    resources = [(self, my_url)]
-
-    d = self.findChildren(depth, request, lambda x, y: resources.append((x, y)), (davxml.Read(),), inherited_aces=filtered_aces)
-    x = waitForDeferred(d)
-    yield x
-    x.getResult()
-
-    for resource, uri in resources:
-        if search_properties is "names":
-            try:
-                resource_properties = waitForDeferred(resource.listProperties(request))
-                yield resource_properties
-                resource_properties = resource_properties.getResult()
-            except:
-                log.err("Unable to get properties for resource %r" % (resource,))
-                raise
-
-            properties_by_status = {
-                responsecode.OK: [propertyName(p) for p in resource_properties]
-            }
-        else:
-            properties_by_status = {
-                responsecode.OK        : [],
-                responsecode.NOT_FOUND : [],
-            }
-
-            if search_properties is "all":
-                properties_to_enumerate = waitForDeferred(resource.listAllprop(request))
-                yield properties_to_enumerate
-                properties_to_enumerate = properties_to_enumerate.getResult()
-            else:
-                properties_to_enumerate = search_properties
-
-            for property in properties_to_enumerate:
-                has = waitForDeferred(resource.hasProperty(property, request))
-                yield has
-                has = has.getResult()
-                if has:
-                    try:
-                        resource_property = waitForDeferred(resource.readProperty(property, request))
-                        yield resource_property
-                        resource_property = resource_property.getResult()
-                    except:
-                        f = Failure()
-
-                        log.err("Error reading property %r for resource %s: %s" % (property, uri, f.value))
-
-                        status = statusForFailure(f, "getting property: %s" % (property,))
-                        if status not in properties_by_status:
-                            properties_by_status[status] = []
-                        properties_by_status[status].append(propertyName(property))
-                    else:
-                        properties_by_status[responsecode.OK].append(resource_property)
-                else:
-                    properties_by_status[responsecode.NOT_FOUND].append(propertyName(property))
-
-        propstats = []
-
-        for status in properties_by_status:
-            properties = properties_by_status[status]
-            if not properties: continue
-
-            xml_status    = davxml.Status.fromResponseCode(status)
-            xml_container = davxml.PropertyContainer(*properties)
-            xml_propstat  = davxml.PropertyStatus(xml_container, xml_status)
-
-            propstats.append(xml_propstat)
-
-        xml_resource = davxml.HRef(uri)
-        xml_response = davxml.PropertyStatusResponse(xml_resource, *propstats)
-
-        xml_responses.append(xml_response)
-
-    #
-    # Return response
-    #
-    yield MultiStatusResponse(xml_responses)
-
-http_PROPFIND = deferredGenerator(http_PROPFIND)
-
-##
-# Utilities
-##
-
-def propertyName(name):
-    property_namespace, property_name = name
-    pname = davxml.WebDAVUnknownElement()
-    pname.namespace = property_namespace
-    pname.name = property_name
-    return pname

Copied: CalendarServer/trunk/twext/web2/dav/method/propfind.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/propfind.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/propfind.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/propfind.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,209 @@
+# -*- test-case-name: twext.web2.dav.test.test_prop.PROP.test_PROPFIND -*-
+##
+# 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
+##
+
+"""
+WebDAV PROPFIND method
+"""
+
+__all__ = [
+    "http_PROPFIND",
+    "propertyName",
+]
+
+from twisted.python import log
+from twisted.python.failure import Failure
+from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twext.web2.http import HTTPError
+from twext.web2 import responsecode
+from twext.web2.http import StatusResponse
+from twext.web2.dav import davxml
+from twext.web2.dav.http import MultiStatusResponse, statusForFailure
+from twext.web2.dav.util import normalizeURL, davXMLFromStream
+
+def http_PROPFIND(self, request):
+    """
+    Respond to a PROPFIND request. (RFC 2518, section 8.1)
+    """
+    if not self.exists():
+        log.err("File not found: %s" % (self.fp.path,))
+        raise HTTPError(responsecode.NOT_FOUND)
+
+    #
+    # Check authentication and access controls
+    #
+    x = waitForDeferred(self.authorize(request, (davxml.Read(),)))
+    yield x
+    x.getResult()
+
+    #
+    # Read request body
+    #
+    try:
+        doc = waitForDeferred(davXMLFromStream(request.stream))
+        yield doc
+        doc = doc.getResult()
+    except ValueError, e:
+        log.err("Error while handling PROPFIND body: %s" % (e,))
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
+
+    if doc is None:
+        # No request body means get all properties.
+        search_properties = "all"
+    else:
+        #
+        # Parse request
+        #
+        find = doc.root_element
+        if not isinstance(find, davxml.PropertyFind):
+            error = ("Non-%s element in PROPFIND request body: %s"
+                     % (davxml.PropertyFind.sname(), find))
+            log.err(error)
+            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
+
+        container = find.children[0]
+
+        if isinstance(container, davxml.AllProperties):
+            # Get all properties
+            search_properties = "all"
+        elif isinstance(container, davxml.PropertyName):
+            # Get names only
+            search_properties = "names"
+        elif isinstance(container, davxml.PropertyContainer):
+            properties = container.children
+            search_properties = [(p.namespace, p.name) for p in properties]
+        else:
+            raise AssertionError("Unexpected element type in %s: %s"
+                                 % (davxml.PropertyFind.sname(), container))
+
+    #
+    # Generate XML output stream
+    #
+    request_uri = request.uri
+    depth = request.headers.getHeader("depth", "infinity")
+
+    xml_responses = []
+
+    # FIXME: take advantage of the new generative properties of findChildren
+
+    my_url = normalizeURL(request_uri)
+    if self.isCollection() and not my_url.endswith("/"):
+        my_url += "/"
+
+    # 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.
+    filtered_aces = waitForDeferred(self.inheritedACEsforChildren(request))
+    yield filtered_aces
+    filtered_aces = filtered_aces.getResult()
+
+    resources = [(self, my_url)]
+
+    d = self.findChildren(depth, request, lambda x, y: resources.append((x, y)), (davxml.Read(),), inherited_aces=filtered_aces)
+    x = waitForDeferred(d)
+    yield x
+    x.getResult()
+
+    for resource, uri in resources:
+        if search_properties is "names":
+            try:
+                resource_properties = waitForDeferred(resource.listProperties(request))
+                yield resource_properties
+                resource_properties = resource_properties.getResult()
+            except:
+                log.err("Unable to get properties for resource %r" % (resource,))
+                raise
+
+            properties_by_status = {
+                responsecode.OK: [propertyName(p) for p in resource_properties]
+            }
+        else:
+            properties_by_status = {
+                responsecode.OK        : [],
+                responsecode.NOT_FOUND : [],
+            }
+
+            if search_properties is "all":
+                properties_to_enumerate = waitForDeferred(resource.listAllprop(request))
+                yield properties_to_enumerate
+                properties_to_enumerate = properties_to_enumerate.getResult()
+            else:
+                properties_to_enumerate = search_properties
+
+            for property in properties_to_enumerate:
+                has = waitForDeferred(resource.hasProperty(property, request))
+                yield has
+                has = has.getResult()
+                if has:
+                    try:
+                        resource_property = waitForDeferred(resource.readProperty(property, request))
+                        yield resource_property
+                        resource_property = resource_property.getResult()
+                    except:
+                        f = Failure()
+
+                        log.err("Error reading property %r for resource %s: %s" % (property, uri, f.value))
+
+                        status = statusForFailure(f, "getting property: %s" % (property,))
+                        if status not in properties_by_status:
+                            properties_by_status[status] = []
+                        properties_by_status[status].append(propertyName(property))
+                    else:
+                        properties_by_status[responsecode.OK].append(resource_property)
+                else:
+                    properties_by_status[responsecode.NOT_FOUND].append(propertyName(property))
+
+        propstats = []
+
+        for status in properties_by_status:
+            properties = properties_by_status[status]
+            if not properties: continue
+
+            xml_status    = davxml.Status.fromResponseCode(status)
+            xml_container = davxml.PropertyContainer(*properties)
+            xml_propstat  = davxml.PropertyStatus(xml_container, xml_status)
+
+            propstats.append(xml_propstat)
+
+        xml_resource = davxml.HRef(uri)
+        xml_response = davxml.PropertyStatusResponse(xml_resource, *propstats)
+
+        xml_responses.append(xml_response)
+
+    #
+    # Return response
+    #
+    yield MultiStatusResponse(xml_responses)
+
+http_PROPFIND = deferredGenerator(http_PROPFIND)
+
+##
+# Utilities
+##
+
+def propertyName(name):
+    property_namespace, property_name = name
+    pname = davxml.WebDAVUnknownElement()
+    pname.namespace = property_namespace
+    pname.name = property_name
+    return pname

Deleted: CalendarServer/trunk/twext/web2/dav/method/proppatch.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/proppatch.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/proppatch.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,189 +0,0 @@
-# -*- test-case-name: twext.web2.dav.test.test_prop.PROP.test_PROPPATCH -*-
-##
-# 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
-##
-
-"""
-WebDAV-aware static resources.
-"""
-
-__all__ = ["http_PROPPATCH"]
-
-from twisted.python import log
-from twisted.python.failure import Failure
-from twisted.internet.defer import deferredGenerator, waitForDeferred
-from twext.web2 import responsecode
-from twext.web2.http import HTTPError, StatusResponse
-from twext.web2.dav import davxml
-from twext.web2.dav.http import MultiStatusResponse, PropertyStatusResponseQueue
-from twext.web2.dav.util import davXMLFromStream
-
-def http_PROPPATCH(self, request):
-    """
-    Respond to a PROPPATCH request. (RFC 2518, section 8.2)
-    """
-    if not self.fp.exists():
-        log.err("File not found: %s" % (self.fp.path,))
-        raise HTTPError(responsecode.NOT_FOUND)
-
-    x = waitForDeferred(self.authorize(request, (davxml.WriteProperties(),)))
-    yield x
-    x.getResult()
-
-    #
-    # Read request body
-    #
-    try:
-        doc = waitForDeferred(davXMLFromStream(request.stream))
-        yield doc
-        doc = doc.getResult()
-    except ValueError, e:
-        log.err("Error while handling PROPPATCH body: %s" % (e,))
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
-
-    if doc is None:
-        error = "Request XML body is required."
-        log.err(error)
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
-
-    #
-    # Parse request
-    #
-    update = doc.root_element
-    if not isinstance(update, davxml.PropertyUpdate):
-        error = ("Request XML body must be a propertyupdate element."
-                 % (davxml.PropertyUpdate.sname(),))
-        log.err(error)
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
-
-    responses = PropertyStatusResponseQueue("PROPPATCH", request.uri, responsecode.NO_CONTENT)
-    undoActions = []
-    gotError = False
-
-    try:
-        #
-        # Update properties
-        #
-        for setOrRemove in update.children:
-            assert len(setOrRemove.children) == 1
-
-            container = setOrRemove.children[0]
-
-            assert isinstance(container, davxml.PropertyContainer)
-
-            properties = container.children
-
-            def do(action, property):
-                """
-                Perform action(property, request) while maintaining an
-                undo queue.
-                """
-                has = waitForDeferred(self.hasProperty(property, request))
-                yield has
-                has = has.getResult()
-
-                if has:
-                    oldProperty = waitForDeferred(self.readProperty(property, request))
-                    yield oldProperty
-                    oldProperty = oldProperty.getResult()
-
-                    def undo():
-                        return self.writeProperty(oldProperty, request)
-                else:
-                    def undo():
-                        return self.removeProperty(property, request)
-
-                try:
-                    x = waitForDeferred(action(property, request))
-                    yield x
-                    x.getResult()
-                except KeyError, e:
-                    # Convert KeyError exception into HTTPError
-                    responses.add(
-                        Failure(exc_value=HTTPError(StatusResponse(responsecode.FORBIDDEN, str(e)))),
-                        property
-                    )
-                    yield False
-                    return
-                except:
-                    responses.add(Failure(), property)
-                    yield False
-                    return
-                else:
-                    responses.add(responsecode.OK, property)
-
-                    # Only add undo action for those that succeed because those that fail will not have changed               
-                    undoActions.append(undo)
-
-                    yield True
-                    return
-
-            do = deferredGenerator(do)
-
-            if isinstance(setOrRemove, davxml.Set):
-                for property in properties:
-                    ok = waitForDeferred(do(self.writeProperty, property))
-                    yield ok
-                    ok = ok.getResult()
-                    if not ok:
-                        gotError = True
-            elif isinstance(setOrRemove, davxml.Remove):
-                for property in properties:
-                    ok = waitForDeferred(do(self.removeProperty, property))
-                    yield ok
-                    ok = ok.getResult()
-                    if not ok:
-                        gotError = True
-            else:
-                raise AssertionError("Unknown child of PropertyUpdate: %s" % (setOrRemove,))
-    except:
-        #
-        # If there is an error, we have to back out whatever we have
-        # operations we have done because PROPPATCH is an
-        # all-or-nothing request.
-        # We handle the first one here, and then re-raise to handle the
-        # rest in the containing scope.
-        #
-        for action in undoActions:
-            x = waitForDeferred(action())
-            yield x
-            x.getResult()
-        raise
-
-    #
-    # If we had an error we need to undo any changes that did succeed and change status of
-    # those to 424 Failed Dependency.
-    #
-    if gotError:
-        for action in undoActions:
-            x = waitForDeferred(action())
-            yield x
-            x.getResult()
-        responses.error()
-
-    #
-    # Return response
-    #
-    yield MultiStatusResponse([responses.response()])
-
-http_PROPPATCH = deferredGenerator(http_PROPPATCH)

Copied: CalendarServer/trunk/twext/web2/dav/method/proppatch.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/proppatch.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/proppatch.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/proppatch.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,189 @@
+# -*- test-case-name: twext.web2.dav.test.test_prop.PROP.test_PROPPATCH -*-
+##
+# 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
+##
+
+"""
+WebDAV-aware static resources.
+"""
+
+__all__ = ["http_PROPPATCH"]
+
+from twisted.python import log
+from twisted.python.failure import Failure
+from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2.dav import davxml
+from twext.web2.dav.http import MultiStatusResponse, PropertyStatusResponseQueue
+from twext.web2.dav.util import davXMLFromStream
+
+def http_PROPPATCH(self, request):
+    """
+    Respond to a PROPPATCH request. (RFC 2518, section 8.2)
+    """
+    if not self.fp.exists():
+        log.err("File not found: %s" % (self.fp.path,))
+        raise HTTPError(responsecode.NOT_FOUND)
+
+    x = waitForDeferred(self.authorize(request, (davxml.WriteProperties(),)))
+    yield x
+    x.getResult()
+
+    #
+    # Read request body
+    #
+    try:
+        doc = waitForDeferred(davXMLFromStream(request.stream))
+        yield doc
+        doc = doc.getResult()
+    except ValueError, e:
+        log.err("Error while handling PROPPATCH body: %s" % (e,))
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
+
+    if doc is None:
+        error = "Request XML body is required."
+        log.err(error)
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
+
+    #
+    # Parse request
+    #
+    update = doc.root_element
+    if not isinstance(update, davxml.PropertyUpdate):
+        error = ("Request XML body must be a propertyupdate element."
+                 % (davxml.PropertyUpdate.sname(),))
+        log.err(error)
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
+
+    responses = PropertyStatusResponseQueue("PROPPATCH", request.uri, responsecode.NO_CONTENT)
+    undoActions = []
+    gotError = False
+
+    try:
+        #
+        # Update properties
+        #
+        for setOrRemove in update.children:
+            assert len(setOrRemove.children) == 1
+
+            container = setOrRemove.children[0]
+
+            assert isinstance(container, davxml.PropertyContainer)
+
+            properties = container.children
+
+            def do(action, property):
+                """
+                Perform action(property, request) while maintaining an
+                undo queue.
+                """
+                has = waitForDeferred(self.hasProperty(property, request))
+                yield has
+                has = has.getResult()
+
+                if has:
+                    oldProperty = waitForDeferred(self.readProperty(property, request))
+                    yield oldProperty
+                    oldProperty = oldProperty.getResult()
+
+                    def undo():
+                        return self.writeProperty(oldProperty, request)
+                else:
+                    def undo():
+                        return self.removeProperty(property, request)
+
+                try:
+                    x = waitForDeferred(action(property, request))
+                    yield x
+                    x.getResult()
+                except KeyError, e:
+                    # Convert KeyError exception into HTTPError
+                    responses.add(
+                        Failure(exc_value=HTTPError(StatusResponse(responsecode.FORBIDDEN, str(e)))),
+                        property
+                    )
+                    yield False
+                    return
+                except:
+                    responses.add(Failure(), property)
+                    yield False
+                    return
+                else:
+                    responses.add(responsecode.OK, property)
+
+                    # Only add undo action for those that succeed because those that fail will not have changed               
+                    undoActions.append(undo)
+
+                    yield True
+                    return
+
+            do = deferredGenerator(do)
+
+            if isinstance(setOrRemove, davxml.Set):
+                for property in properties:
+                    ok = waitForDeferred(do(self.writeProperty, property))
+                    yield ok
+                    ok = ok.getResult()
+                    if not ok:
+                        gotError = True
+            elif isinstance(setOrRemove, davxml.Remove):
+                for property in properties:
+                    ok = waitForDeferred(do(self.removeProperty, property))
+                    yield ok
+                    ok = ok.getResult()
+                    if not ok:
+                        gotError = True
+            else:
+                raise AssertionError("Unknown child of PropertyUpdate: %s" % (setOrRemove,))
+    except:
+        #
+        # If there is an error, we have to back out whatever we have
+        # operations we have done because PROPPATCH is an
+        # all-or-nothing request.
+        # We handle the first one here, and then re-raise to handle the
+        # rest in the containing scope.
+        #
+        for action in undoActions:
+            x = waitForDeferred(action())
+            yield x
+            x.getResult()
+        raise
+
+    #
+    # If we had an error we need to undo any changes that did succeed and change status of
+    # those to 424 Failed Dependency.
+    #
+    if gotError:
+        for action in undoActions:
+            x = waitForDeferred(action())
+            yield x
+            x.getResult()
+        responses.error()
+
+    #
+    # Return response
+    #
+    yield MultiStatusResponse([responses.response()])
+
+http_PROPPATCH = deferredGenerator(http_PROPPATCH)

Deleted: CalendarServer/trunk/twext/web2/dav/method/put.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/put.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/put.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,111 +0,0 @@
-# -*- test-case-name: twext.web2.dav.test.test_put -*-
-##
-# 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
-##
-
-"""
-WebDAV PUT method
-"""
-
-__all__ = ["preconditions_PUT", "http_PUT"]
-
-from twisted.python import log
-from twisted.internet.defer import deferredGenerator, waitForDeferred
-from twext.web2 import responsecode
-from twext.web2.http import HTTPError, StatusResponse
-from twext.web2.dav import davxml
-from twext.web2.dav.method import put_common
-from twext.web2.dav.util import parentForURL
-
-def preconditions_PUT(self, request):
-    #
-    # Check authentication and access controls
-    #
-    if self.exists():
-        x = waitForDeferred(self.authorize(request, (davxml.WriteContent(),)))
-        yield x
-        x.getResult()
-    else:
-        parent = waitForDeferred(request.locateResource(parentForURL(request.uri)))
-        yield parent
-        parent = parent.getResult()
-
-        x = waitForDeferred(parent.authorize(request, (davxml.Bind(),)))
-        yield x
-        x.getResult()
-
-    if self.fp.exists():
-        if not self.fp.isfile():
-            log.err("Unable to PUT to non-file: %s" % (self.fp.path,))
-            raise HTTPError(StatusResponse(
-                responsecode.FORBIDDEN,
-                "The requested resource exists but is not backed by a regular file."
-            ))
-        resource_is_new = False
-    else:
-        if not self.fp.parent().isdir():
-            log.err("No such directory: %s" % (self.fp.path,))
-            raise HTTPError(StatusResponse(
-                responsecode.CONFLICT,
-                "Parent collection resource does not exist."
-            ))
-        resource_is_new = True
-
-    #
-    # HTTP/1.1 (RFC 2068, section 9.6) requires that we respond with a Not
-    # Implemented error if we get a Content-* header which we don't
-    # recognize and handle properly.
-    #
-    for header, value in request.headers.getAllRawHeaders():
-        if header.startswith("Content-") and header not in (
-           #"Content-Base",     # Doesn't make sense in PUT?
-           #"Content-Encoding", # Requires that we decode it?
-            "Content-Language",
-            "Content-Length",
-           #"Content-Location", # Doesn't make sense in PUT?
-            "Content-MD5",
-           #"Content-Range",    # FIXME: Need to implement this
-            "Content-Type",
-        ):
-            log.err("Client sent unrecognized content header in PUT request: %s"
-                    % (header,))
-            raise HTTPError(StatusResponse(
-                responsecode.NOT_IMPLEMENTED,
-                "Unrecognized content header %r in request." % (header,)
-            ))
-
-preconditions_PUT = deferredGenerator(preconditions_PUT)
-
-def http_PUT(self, request):
-    """
-    Respond to a PUT request. (RFC 2518, section 8.7)
-    """
-    log.msg("Writing request stream to %s" % (self.fp.path,))
-
-    #
-    # Don't pass in the request URI, since PUT isn't specified to be able
-    # to return a MULTI_STATUS response, which is WebDAV-specific (and PUT is
-    # not).
-    #
-    #return put(request.stream, self.fp)
-    return put_common.storeResource(request, destination=self, destination_uri=request.uri)

Copied: CalendarServer/trunk/twext/web2/dav/method/put.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/put.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/put.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/put.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,111 @@
+# -*- test-case-name: twext.web2.dav.test.test_put -*-
+##
+# 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
+##
+
+"""
+WebDAV PUT method
+"""
+
+__all__ = ["preconditions_PUT", "http_PUT"]
+
+from twisted.python import log
+from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2.dav import davxml
+from twext.web2.dav.method import put_common
+from twext.web2.dav.util import parentForURL
+
+def preconditions_PUT(self, request):
+    #
+    # Check authentication and access controls
+    #
+    if self.exists():
+        x = waitForDeferred(self.authorize(request, (davxml.WriteContent(),)))
+        yield x
+        x.getResult()
+    else:
+        parent = waitForDeferred(request.locateResource(parentForURL(request.uri)))
+        yield parent
+        parent = parent.getResult()
+
+        x = waitForDeferred(parent.authorize(request, (davxml.Bind(),)))
+        yield x
+        x.getResult()
+
+    if self.fp.exists():
+        if not self.fp.isfile():
+            log.err("Unable to PUT to non-file: %s" % (self.fp.path,))
+            raise HTTPError(StatusResponse(
+                responsecode.FORBIDDEN,
+                "The requested resource exists but is not backed by a regular file."
+            ))
+        resource_is_new = False
+    else:
+        if not self.fp.parent().isdir():
+            log.err("No such directory: %s" % (self.fp.path,))
+            raise HTTPError(StatusResponse(
+                responsecode.CONFLICT,
+                "Parent collection resource does not exist."
+            ))
+        resource_is_new = True
+
+    #
+    # HTTP/1.1 (RFC 2068, section 9.6) requires that we respond with a Not
+    # Implemented error if we get a Content-* header which we don't
+    # recognize and handle properly.
+    #
+    for header, value in request.headers.getAllRawHeaders():
+        if header.startswith("Content-") and header not in (
+           #"Content-Base",     # Doesn't make sense in PUT?
+           #"Content-Encoding", # Requires that we decode it?
+            "Content-Language",
+            "Content-Length",
+           #"Content-Location", # Doesn't make sense in PUT?
+            "Content-MD5",
+           #"Content-Range",    # FIXME: Need to implement this
+            "Content-Type",
+        ):
+            log.err("Client sent unrecognized content header in PUT request: %s"
+                    % (header,))
+            raise HTTPError(StatusResponse(
+                responsecode.NOT_IMPLEMENTED,
+                "Unrecognized content header %r in request." % (header,)
+            ))
+
+preconditions_PUT = deferredGenerator(preconditions_PUT)
+
+def http_PUT(self, request):
+    """
+    Respond to a PUT request. (RFC 2518, section 8.7)
+    """
+    log.msg("Writing request stream to %s" % (self.fp.path,))
+
+    #
+    # Don't pass in the request URI, since PUT isn't specified to be able
+    # to return a MULTI_STATUS response, which is WebDAV-specific (and PUT is
+    # not).
+    #
+    #return put(request.stream, self.fp)
+    return put_common.storeResource(request, destination=self, destination_uri=request.uri)

Deleted: CalendarServer/trunk/twext/web2/dav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/put_common.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/put_common.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,265 +0,0 @@
-##
-# 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__ = ["storeResource"]
-
-from twisted.internet.defer import deferredGenerator, maybeDeferred, waitForDeferred
-from twisted.python import failure, log
-from twext.python.filepath import CachingFilePath as FilePath
-from twext.web2 import responsecode
-from twext.web2.dav import davxml
-from twext.web2.dav.element.base import dav_namespace
-from twext.web2.dav.fileop import copy, delete, put
-from twext.web2.dav.http import ErrorResponse
-from twext.web2.dav.resource import TwistedGETContentMD5
-from twext.web2.dav.stream import MD5StreamWrapper
-from twext.web2.http import HTTPError
-from twext.web2.http_headers import generateContentType
-from twext.web2.iweb import IResponse
-from twext.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{twext.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/twext/web2/dav/method/put_common.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/put_common.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/put_common.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/put_common.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -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__ = ["storeResource"]
+
+from twisted.internet.defer import deferredGenerator, maybeDeferred, waitForDeferred
+from twisted.python import failure, log
+from twext.python.filepath import CachingFilePath as FilePath
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.element.base import dav_namespace
+from twext.web2.dav.fileop import copy, delete, put
+from twext.web2.dav.http import ErrorResponse
+from twext.web2.dav.resource import TwistedGETContentMD5
+from twext.web2.dav.stream import MD5StreamWrapper
+from twext.web2.http import HTTPError
+from twext.web2.http_headers import generateContentType
+from twext.web2.iweb import IResponse
+from twext.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{twext.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)

Deleted: CalendarServer/trunk/twext/web2/dav/method/report.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/report.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/report.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,147 +0,0 @@
-# -*- test-case-name: twext.web2.dav.test.test_report -*-
-##
-# 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
-##
-
-"""
-WebDAV REPORT method
-"""
-
-__all__ = [
-    "max_number_of_matches",
-    "NumberOfMatchesWithinLimits",
-    "http_REPORT",
-]
-
-import string
-
-from twisted.python import log
-from twisted.internet.defer import deferredGenerator, waitForDeferred
-from twext.web2 import responsecode
-from twext.web2.http import HTTPError, StatusResponse
-from twext.web2.dav import davxml
-from twext.web2.dav.element.parser import lookupElement
-from twext.web2.dav.http import ErrorResponse
-from twext.web2.dav.util import davXMLFromStream
-
-max_number_of_matches = 500
-
-class NumberOfMatchesWithinLimits(Exception):
-    
-    def __init__(self, limit):
-        
-        super(NumberOfMatchesWithinLimits, self).__init__()
-        self.limit = limit
-        
-    def maxLimit(self):
-        return self.limit
-
-def http_REPORT(self, request):
-    """
-    Respond to a REPORT request. (RFC 3253, section 3.6)
-    """
-    if not self.fp.exists():
-        log.err("File not found: %s" % (self.fp.path,))
-        raise HTTPError(responsecode.NOT_FOUND)
-
-    #
-    # Check authentication and access controls
-    #
-    x = waitForDeferred(self.authorize(request, (davxml.Read(),)))
-    yield x
-    x.getResult()
-
-    #
-    # Read request body
-    #
-    try:
-        doc = waitForDeferred(davXMLFromStream(request.stream))
-        yield doc
-        doc = doc.getResult()
-    except ValueError, e:
-        log.err("Error while handling REPORT body: %s" % (e,))
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
-
-    if doc is None:
-        raise HTTPError(StatusResponse(
-            responsecode.BAD_REQUEST,
-            "REPORT request body may not be empty"
-        ))
-
-    #
-    # Parse request
-    #
-    namespace = doc.root_element.namespace
-    name = doc.root_element.name
-
-    ok = string.ascii_letters + string.digits + "_"
-
-    def to_method(s):
-        out = []
-        for c in s:
-            if c in ok:
-                out.append(c)
-            else:
-                out.append("_")
-        return "report_" + "".join(out)
-
-    if namespace:
-        method_name = to_method("_".join((namespace, name)))
-
-        if namespace == davxml.dav_namespace:
-            request.submethod = "DAV:" + name
-        else:
-            request.submethod = "{%s}%s" % (namespace, name)
-    else:
-        method_name = to_method(name)
-
-        request.submethod = name
-
-    try:
-        method = getattr(self, method_name)
-        
-        # Also double-check via supported-reports property
-        reports = self.supportedReports()
-        test = lookupElement((namespace, name))
-        if not test:
-            raise AttributeError()
-        test = davxml.Report(test())
-        if test not in reports:
-            raise AttributeError()
-    except AttributeError:
-        #
-        # Requested report is not supported.
-        #
-        log.err("Unsupported REPORT {%s}%s for resource %s (no method %s)"
-                % (namespace, name, self, method_name))
-
-        raise HTTPError(ErrorResponse(
-            responsecode.FORBIDDEN,
-            davxml.SupportedReport()
-        ))
-
-    d = waitForDeferred(method(request, doc.root_element))
-    yield d
-    yield d.getResult()
-
-http_REPORT = deferredGenerator(http_REPORT)

Copied: CalendarServer/trunk/twext/web2/dav/method/report.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/report.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/report.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/report.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,147 @@
+# -*- test-case-name: twext.web2.dav.test.test_report -*-
+##
+# 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
+##
+
+"""
+WebDAV REPORT method
+"""
+
+__all__ = [
+    "max_number_of_matches",
+    "NumberOfMatchesWithinLimits",
+    "http_REPORT",
+]
+
+import string
+
+from twisted.python import log
+from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2.dav import davxml
+from twext.web2.dav.element.parser import lookupElement
+from twext.web2.dav.http import ErrorResponse
+from twext.web2.dav.util import davXMLFromStream
+
+max_number_of_matches = 500
+
+class NumberOfMatchesWithinLimits(Exception):
+    
+    def __init__(self, limit):
+        
+        super(NumberOfMatchesWithinLimits, self).__init__()
+        self.limit = limit
+        
+    def maxLimit(self):
+        return self.limit
+
+def http_REPORT(self, request):
+    """
+    Respond to a REPORT request. (RFC 3253, section 3.6)
+    """
+    if not self.fp.exists():
+        log.err("File not found: %s" % (self.fp.path,))
+        raise HTTPError(responsecode.NOT_FOUND)
+
+    #
+    # Check authentication and access controls
+    #
+    x = waitForDeferred(self.authorize(request, (davxml.Read(),)))
+    yield x
+    x.getResult()
+
+    #
+    # Read request body
+    #
+    try:
+        doc = waitForDeferred(davXMLFromStream(request.stream))
+        yield doc
+        doc = doc.getResult()
+    except ValueError, e:
+        log.err("Error while handling REPORT body: %s" % (e,))
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
+
+    if doc is None:
+        raise HTTPError(StatusResponse(
+            responsecode.BAD_REQUEST,
+            "REPORT request body may not be empty"
+        ))
+
+    #
+    # Parse request
+    #
+    namespace = doc.root_element.namespace
+    name = doc.root_element.name
+
+    ok = string.ascii_letters + string.digits + "_"
+
+    def to_method(s):
+        out = []
+        for c in s:
+            if c in ok:
+                out.append(c)
+            else:
+                out.append("_")
+        return "report_" + "".join(out)
+
+    if namespace:
+        method_name = to_method("_".join((namespace, name)))
+
+        if namespace == davxml.dav_namespace:
+            request.submethod = "DAV:" + name
+        else:
+            request.submethod = "{%s}%s" % (namespace, name)
+    else:
+        method_name = to_method(name)
+
+        request.submethod = name
+
+    try:
+        method = getattr(self, method_name)
+        
+        # Also double-check via supported-reports property
+        reports = self.supportedReports()
+        test = lookupElement((namespace, name))
+        if not test:
+            raise AttributeError()
+        test = davxml.Report(test())
+        if test not in reports:
+            raise AttributeError()
+    except AttributeError:
+        #
+        # Requested report is not supported.
+        #
+        log.err("Unsupported REPORT {%s}%s for resource %s (no method %s)"
+                % (namespace, name, self, method_name))
+
+        raise HTTPError(ErrorResponse(
+            responsecode.FORBIDDEN,
+            davxml.SupportedReport()
+        ))
+
+    d = waitForDeferred(method(request, doc.root_element))
+    yield d
+    yield d.getResult()
+
+http_REPORT = deferredGenerator(http_REPORT)

Deleted: CalendarServer/trunk/twext/web2/dav/method/report_acl_principal_prop_set.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/report_acl_principal_prop_set.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/report_acl_principal_prop_set.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,152 +0,0 @@
-# -*- test-case-name: twext.web2.dav.test.test_report_expand -*-
-##
-# Copyright (c) 2006 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
-##
-
-"""
-WebDAV acl-prinicpal-prop-set report
-"""
-
-__all__ = ["report_DAV__acl_principal_prop_set"]
-
-from twisted.python import log
-from twisted.internet.defer import deferredGenerator, waitForDeferred
-from twext.web2 import responsecode
-from twext.web2.http import HTTPError, StatusResponse
-from twext.web2.dav import davxml
-from twext.web2.dav.davxml import dav_namespace
-from twext.web2.dav.http import ErrorResponse
-from twext.web2.dav.http import MultiStatusResponse
-from twext.web2.dav.method import prop_common
-from twext.web2.dav.method.report import NumberOfMatchesWithinLimits
-from twext.web2.dav.method.report import max_number_of_matches
-
-def report_DAV__acl_principal_prop_set(self, request, acl_prinicpal_prop_set):
-    """
-    Generate an acl-prinicpal-prop-set REPORT. (RFC 3744, section 9.2)
-    """
-    # Verify root element
-    if not isinstance(acl_prinicpal_prop_set, davxml.ACLPrincipalPropSet):
-        raise ValueError("%s expected as root element, not %s."
-                         % (davxml.ACLPrincipalPropSet.sname(), acl_prinicpal_prop_set.sname()))
-
-    # Depth must be "0"
-    depth = request.headers.getHeader("depth", "0")
-    if depth != "0":
-        log.err("Error in prinicpal-prop-set REPORT, Depth set to %s" % (depth,))
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
-    
-    #
-    # Check authentication and access controls
-    #
-    x = waitForDeferred(self.authorize(request, (davxml.ReadACL(),)))
-    yield x
-    x.getResult()
-
-    # Get a single DAV:prop element from the REPORT request body
-    propertiesForResource = None
-    propElement = None
-    for child in acl_prinicpal_prop_set.children:
-        if child.qname() == ("DAV:", "prop"):
-            if propertiesForResource is not None:
-                log.err("Only one DAV:prop element allowed")
-                raise HTTPError(StatusResponse(
-                    responsecode.BAD_REQUEST,
-                    "Only one DAV:prop element allowed"
-                ))
-            propertiesForResource = prop_common.propertyListForResource
-            propElement = child
-
-    if propertiesForResource is None:
-        log.err("Error in acl-principal-prop-set REPORT, no DAV:prop element")
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "No DAV:prop element"))
-
-    # Enumerate principals on ACL in current resource
-    principals = []
-
-    acl = waitForDeferred(self.accessControlList(request))
-    yield acl
-    acl = acl.getResult()
-
-    for ace in acl.children:
-        resolved = waitForDeferred(self.resolvePrincipal(ace.principal.children[0], request))
-        yield resolved
-        resolved = resolved.getResult()
-        if resolved is not None and resolved not in principals:
-            principals.append(resolved)
-
-    # Run report for each referenced principal
-    try:
-        responses = []
-        matchcount = 0
-        for principal in principals:
-            # Check size of results is within limit
-            matchcount += 1
-            if matchcount > max_number_of_matches:
-                raise NumberOfMatchesWithinLimits(max_number_of_matches)
-
-            resource = waitForDeferred(request.locateResource(str(principal)))
-            yield resource
-            resource = resource.getResult()
-
-            if resource is not None:
-                #
-                # Check authentication and access controls
-                #
-                x = waitForDeferred(resource.authorize(request, (davxml.Read(),)))
-                yield x
-                try:
-                    x.getResult()
-                except HTTPError:
-                    responses.append(davxml.StatusResponse(
-                        principal,
-                        davxml.Status.fromResponseCode(responsecode.FORBIDDEN)
-                    ))
-                else:
-                    d = waitForDeferred(prop_common.responseForHref(
-                        request,
-                        responses,
-                        principal,
-                        resource,
-                        propertiesForResource,
-                        propElement
-                    ))
-                    yield d
-                    d.getResult()
-            else:
-                log.err("Requested principal resource not found: %s" % (str(principal),))
-                responses.append(davxml.StatusResponse(
-                    principal,
-                    davxml.Status.fromResponseCode(responsecode.NOT_FOUND)
-                ))
-
-    except NumberOfMatchesWithinLimits:
-        log.err("Too many matching components")
-        raise HTTPError(ErrorResponse(
-            responsecode.FORBIDDEN,
-            davxml.NumberOfMatchesWithinLimits()
-        ))
-
-    yield MultiStatusResponse(responses)
-
-report_DAV__acl_principal_prop_set = deferredGenerator(report_DAV__acl_principal_prop_set)

Copied: CalendarServer/trunk/twext/web2/dav/method/report_acl_principal_prop_set.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/report_acl_principal_prop_set.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/report_acl_principal_prop_set.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/report_acl_principal_prop_set.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,152 @@
+# -*- test-case-name: twext.web2.dav.test.test_report_expand -*-
+##
+# Copyright (c) 2006 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
+##
+
+"""
+WebDAV acl-prinicpal-prop-set report
+"""
+
+__all__ = ["report_DAV__acl_principal_prop_set"]
+
+from twisted.python import log
+from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2.dav import davxml
+from twext.web2.dav.davxml import dav_namespace
+from twext.web2.dav.http import ErrorResponse
+from twext.web2.dav.http import MultiStatusResponse
+from twext.web2.dav.method import prop_common
+from twext.web2.dav.method.report import NumberOfMatchesWithinLimits
+from twext.web2.dav.method.report import max_number_of_matches
+
+def report_DAV__acl_principal_prop_set(self, request, acl_prinicpal_prop_set):
+    """
+    Generate an acl-prinicpal-prop-set REPORT. (RFC 3744, section 9.2)
+    """
+    # Verify root element
+    if not isinstance(acl_prinicpal_prop_set, davxml.ACLPrincipalPropSet):
+        raise ValueError("%s expected as root element, not %s."
+                         % (davxml.ACLPrincipalPropSet.sname(), acl_prinicpal_prop_set.sname()))
+
+    # Depth must be "0"
+    depth = request.headers.getHeader("depth", "0")
+    if depth != "0":
+        log.err("Error in prinicpal-prop-set REPORT, Depth set to %s" % (depth,))
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
+    
+    #
+    # Check authentication and access controls
+    #
+    x = waitForDeferred(self.authorize(request, (davxml.ReadACL(),)))
+    yield x
+    x.getResult()
+
+    # Get a single DAV:prop element from the REPORT request body
+    propertiesForResource = None
+    propElement = None
+    for child in acl_prinicpal_prop_set.children:
+        if child.qname() == ("DAV:", "prop"):
+            if propertiesForResource is not None:
+                log.err("Only one DAV:prop element allowed")
+                raise HTTPError(StatusResponse(
+                    responsecode.BAD_REQUEST,
+                    "Only one DAV:prop element allowed"
+                ))
+            propertiesForResource = prop_common.propertyListForResource
+            propElement = child
+
+    if propertiesForResource is None:
+        log.err("Error in acl-principal-prop-set REPORT, no DAV:prop element")
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "No DAV:prop element"))
+
+    # Enumerate principals on ACL in current resource
+    principals = []
+
+    acl = waitForDeferred(self.accessControlList(request))
+    yield acl
+    acl = acl.getResult()
+
+    for ace in acl.children:
+        resolved = waitForDeferred(self.resolvePrincipal(ace.principal.children[0], request))
+        yield resolved
+        resolved = resolved.getResult()
+        if resolved is not None and resolved not in principals:
+            principals.append(resolved)
+
+    # Run report for each referenced principal
+    try:
+        responses = []
+        matchcount = 0
+        for principal in principals:
+            # Check size of results is within limit
+            matchcount += 1
+            if matchcount > max_number_of_matches:
+                raise NumberOfMatchesWithinLimits(max_number_of_matches)
+
+            resource = waitForDeferred(request.locateResource(str(principal)))
+            yield resource
+            resource = resource.getResult()
+
+            if resource is not None:
+                #
+                # Check authentication and access controls
+                #
+                x = waitForDeferred(resource.authorize(request, (davxml.Read(),)))
+                yield x
+                try:
+                    x.getResult()
+                except HTTPError:
+                    responses.append(davxml.StatusResponse(
+                        principal,
+                        davxml.Status.fromResponseCode(responsecode.FORBIDDEN)
+                    ))
+                else:
+                    d = waitForDeferred(prop_common.responseForHref(
+                        request,
+                        responses,
+                        principal,
+                        resource,
+                        propertiesForResource,
+                        propElement
+                    ))
+                    yield d
+                    d.getResult()
+            else:
+                log.err("Requested principal resource not found: %s" % (str(principal),))
+                responses.append(davxml.StatusResponse(
+                    principal,
+                    davxml.Status.fromResponseCode(responsecode.NOT_FOUND)
+                ))
+
+    except NumberOfMatchesWithinLimits:
+        log.err("Too many matching components")
+        raise HTTPError(ErrorResponse(
+            responsecode.FORBIDDEN,
+            davxml.NumberOfMatchesWithinLimits()
+        ))
+
+    yield MultiStatusResponse(responses)
+
+report_DAV__acl_principal_prop_set = deferredGenerator(report_DAV__acl_principal_prop_set)

Deleted: CalendarServer/trunk/twext/web2/dav/method/report_expand.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/report_expand.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/report_expand.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,169 +0,0 @@
-# -*- test-case-name: twext.web2.dav.test.test_report_expand -*-
-##
-# 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
-# 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.
-##
-
-"""
-WebDAV expand-property report
-"""
-
-__all__ = ["report_DAV__expand_property"]
-
-from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.python import log
-from twisted.python.failure import Failure
-from twext.web2 import responsecode
-from twext.web2.dav import davxml
-from twext.web2.dav.davxml import dav_namespace
-from twext.web2.dav.http import statusForFailure, MultiStatusResponse
-from twext.web2.dav.method import prop_common
-from twext.web2.dav.method.propfind import propertyName
-from twext.web2.dav.resource import AccessDeniedError
-from twext.web2.dav.util import parentForURL
-from twext.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.
-    """
-    # 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,)))
-    
-    #
-    # Get top level properties to expand and make sure we only have one level
-    #
-    properties = {}
-
-    for property in expand_property.children:
-        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))
-
-        properties[(namespace, name)] = props_to_find
-
-    #
-    # Generate the expanded responses status for each top-level property
-    #
-    properties_by_status = {
-        responsecode.OK        : [],
-        responsecode.NOT_FOUND : [],
-    }
-    
-    filteredaces = None
-    lastParent = None
-
-    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])
-
-            # 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))
-
-                    # 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" % (qname, request.uri, f.value))
-
-            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))
-
-    # 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]
-    ]
-
-    returnValue(MultiStatusResponse((davxml.PropertyStatusResponse(davxml.HRef(request.uri), *propstats),)))

Copied: CalendarServer/trunk/twext/web2/dav/method/report_expand.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/report_expand.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/report_expand.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/report_expand.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,169 @@
+# -*- test-case-name: twext.web2.dav.test.test_report_expand -*-
+##
+# 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
+# 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.
+##
+
+"""
+WebDAV expand-property report
+"""
+
+__all__ = ["report_DAV__expand_property"]
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.python import log
+from twisted.python.failure import Failure
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.davxml import dav_namespace
+from twext.web2.dav.http import statusForFailure, MultiStatusResponse
+from twext.web2.dav.method import prop_common
+from twext.web2.dav.method.propfind import propertyName
+from twext.web2.dav.resource import AccessDeniedError
+from twext.web2.dav.util import parentForURL
+from twext.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.
+    """
+    # 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,)))
+    
+    #
+    # Get top level properties to expand and make sure we only have one level
+    #
+    properties = {}
+
+    for property in expand_property.children:
+        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))
+
+        properties[(namespace, name)] = props_to_find
+
+    #
+    # Generate the expanded responses status for each top-level property
+    #
+    properties_by_status = {
+        responsecode.OK        : [],
+        responsecode.NOT_FOUND : [],
+    }
+    
+    filteredaces = None
+    lastParent = None
+
+    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])
+
+            # 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))
+
+                    # 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" % (qname, request.uri, f.value))
+
+            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))
+
+    # 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]
+    ]
+
+    returnValue(MultiStatusResponse((davxml.PropertyStatusResponse(davxml.HRef(request.uri), *propstats),)))

Deleted: CalendarServer/trunk/twext/web2/dav/method/report_principal_match.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/report_principal_match.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/report_principal_match.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,198 +0,0 @@
-# -*- test-case-name: twext.web2.dav.test.test_report_expand -*-
-##
-# Copyright (c) 2006 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
-##
-
-
-"""
-WebDAV principal-match report
-"""
-
-__all__ = ["report_DAV__principal_match"]
-
-from twisted.python import log
-from twisted.internet.defer import deferredGenerator, waitForDeferred
-from twext.web2 import responsecode
-from twext.web2.http import StatusResponse, HTTPError
-from twext.web2.dav import davxml
-from twext.web2.dav.davxml import dav_namespace
-from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
-from twext.web2.dav.method import prop_common
-from twext.web2.dav.method.report import NumberOfMatchesWithinLimits
-from twext.web2.dav.method.report import max_number_of_matches
-from twext.web2.dav.resource import isPrincipalResource
-
-def report_DAV__principal_match(self, request, principal_match):
-    """
-    Generate a principal-match REPORT. (RFC 3744, section 9.3)
-    """
-    # Verify root element
-    if not isinstance(principal_match, davxml.PrincipalMatch):
-        raise ValueError("%s expected as root element, not %s."
-                         % (davxml.PrincipalMatch.sname(), principal_match.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,)))
-    
-    # Get a single DAV:prop element from the REPORT request body
-    propertiesForResource = None
-    propElement = None
-    principalPropElement = None
-    lookForPrincipals = True
-
-    for child in principal_match.children:
-        if child.qname() == (dav_namespace, "prop"):
-            propertiesForResource = prop_common.propertyListForResource
-            propElement = child
-
-        elif child.qname() == (dav_namespace, "self"):
-            lookForPrincipals = True
-
-        elif child.qname() == (dav_namespace, "principal-property"):
-            # Must have one and only one property in this element
-            if len(child.children) != 1:
-                log.err("Wrong number of properties in DAV:principal-property: %s"
-                        % (len(child.children),))
-                raise HTTPError(StatusResponse(
-                    responsecode.BAD_REQUEST,
-                    "DAV:principal-property must contain exactly one property"
-                ))
-
-            lookForPrincipals = False
-            principalPropElement = child.children[0]
-
-    # Run report for each referenced principal
-    try:
-        responses = []
-        matchcount = 0
-
-        myPrincipalURL = self.currentPrincipal(request).children[0]
-
-        if lookForPrincipals:
-
-            # Find the set of principals that represent "self".
-            
-            # First add "self"
-            principal = waitForDeferred(request.locateResource(str(myPrincipalURL)))
-            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()
-
-            for child, uri in children:
-                # Try to read the requested property from this resource
-                try:
-                    prop = waitForDeferred(child.readProperty(principalPropElement.qname(), request))
-                    yield prop
-                    prop = prop.getResult()
-                    if prop: prop.removeWhitespaceNodes()
-
-                    if prop and len(prop.children) == 1 and isinstance(prop.children[0], davxml.HRef):
-                        # Find principal associated with this property and test it
-                        principal = waitForDeferred(request.locateResource(str(prop.children[0])))
-                        yield principal
-                        principal = principal.getResult()
-
-                        if principal and isPrincipalResource(principal):
-                            d = waitForDeferred(principal.principalMatch(myPrincipalURL))
-                            yield d
-                            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.
-                    pass
-
-    except NumberOfMatchesWithinLimits:
-        log.err("Too many matching components in principal-match report")
-        raise HTTPError(ErrorResponse(
-            responsecode.FORBIDDEN,
-            davxml.NumberOfMatchesWithinLimits()
-        ))
-
-    yield MultiStatusResponse(responses)
-
-report_DAV__principal_match = deferredGenerator(report_DAV__principal_match)

Copied: CalendarServer/trunk/twext/web2/dav/method/report_principal_match.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/report_principal_match.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/report_principal_match.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/report_principal_match.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,198 @@
+# -*- test-case-name: twext.web2.dav.test.test_report_expand -*-
+##
+# Copyright (c) 2006 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
+##
+
+
+"""
+WebDAV principal-match report
+"""
+
+__all__ = ["report_DAV__principal_match"]
+
+from twisted.python import log
+from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twext.web2 import responsecode
+from twext.web2.http import StatusResponse, HTTPError
+from twext.web2.dav import davxml
+from twext.web2.dav.davxml import dav_namespace
+from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
+from twext.web2.dav.method import prop_common
+from twext.web2.dav.method.report import NumberOfMatchesWithinLimits
+from twext.web2.dav.method.report import max_number_of_matches
+from twext.web2.dav.resource import isPrincipalResource
+
+def report_DAV__principal_match(self, request, principal_match):
+    """
+    Generate a principal-match REPORT. (RFC 3744, section 9.3)
+    """
+    # Verify root element
+    if not isinstance(principal_match, davxml.PrincipalMatch):
+        raise ValueError("%s expected as root element, not %s."
+                         % (davxml.PrincipalMatch.sname(), principal_match.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,)))
+    
+    # Get a single DAV:prop element from the REPORT request body
+    propertiesForResource = None
+    propElement = None
+    principalPropElement = None
+    lookForPrincipals = True
+
+    for child in principal_match.children:
+        if child.qname() == (dav_namespace, "prop"):
+            propertiesForResource = prop_common.propertyListForResource
+            propElement = child
+
+        elif child.qname() == (dav_namespace, "self"):
+            lookForPrincipals = True
+
+        elif child.qname() == (dav_namespace, "principal-property"):
+            # Must have one and only one property in this element
+            if len(child.children) != 1:
+                log.err("Wrong number of properties in DAV:principal-property: %s"
+                        % (len(child.children),))
+                raise HTTPError(StatusResponse(
+                    responsecode.BAD_REQUEST,
+                    "DAV:principal-property must contain exactly one property"
+                ))
+
+            lookForPrincipals = False
+            principalPropElement = child.children[0]
+
+    # Run report for each referenced principal
+    try:
+        responses = []
+        matchcount = 0
+
+        myPrincipalURL = self.currentPrincipal(request).children[0]
+
+        if lookForPrincipals:
+
+            # Find the set of principals that represent "self".
+            
+            # First add "self"
+            principal = waitForDeferred(request.locateResource(str(myPrincipalURL)))
+            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()
+
+            for child, uri in children:
+                # Try to read the requested property from this resource
+                try:
+                    prop = waitForDeferred(child.readProperty(principalPropElement.qname(), request))
+                    yield prop
+                    prop = prop.getResult()
+                    if prop: prop.removeWhitespaceNodes()
+
+                    if prop and len(prop.children) == 1 and isinstance(prop.children[0], davxml.HRef):
+                        # Find principal associated with this property and test it
+                        principal = waitForDeferred(request.locateResource(str(prop.children[0])))
+                        yield principal
+                        principal = principal.getResult()
+
+                        if principal and isPrincipalResource(principal):
+                            d = waitForDeferred(principal.principalMatch(myPrincipalURL))
+                            yield d
+                            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.
+                    pass
+
+    except NumberOfMatchesWithinLimits:
+        log.err("Too many matching components in principal-match report")
+        raise HTTPError(ErrorResponse(
+            responsecode.FORBIDDEN,
+            davxml.NumberOfMatchesWithinLimits()
+        ))
+
+    yield MultiStatusResponse(responses)
+
+report_DAV__principal_match = deferredGenerator(report_DAV__principal_match)

Deleted: CalendarServer/trunk/twext/web2/dav/method/report_principal_property_search.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/report_principal_property_search.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/report_principal_property_search.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,186 +0,0 @@
-# -*- test-case-name: twext.web2.dav.test.test_report_expand -*-
-##
-# Copyright (c) 2006 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
-##
-
-"""
-WebDAV prinicpal-property-search report
-"""
-
-__all__ = ["report_DAV__principal_property_search"]
-
-from twisted.python import log
-from twisted.internet.defer import deferredGenerator, waitForDeferred
-from twext.web2 import responsecode
-from twext.web2.http import HTTPError, StatusResponse
-from twext.web2.dav import davxml
-from twext.web2.dav.davxml import dav_namespace
-from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
-from twext.web2.dav.method import prop_common
-from twext.web2.dav.method.report import NumberOfMatchesWithinLimits
-from twext.web2.dav.method.report import max_number_of_matches
-from twext.web2.dav.resource import isPrincipalResource
-
-def report_DAV__principal_property_search(self, request, principal_property_search):
-    """
-    Generate a principal-property-search REPORT. (RFC 3744, section 9.4)
-    """
-
-    # Verify root element
-    if not isinstance(principal_property_search, davxml.PrincipalPropertySearch):
-        raise ValueError("%s expected as root element, not %s."
-                         % (davxml.PrincipalPropertySearch.sname(), principal_property_search.sname()))
-
-    # Only handle Depth: 0
-    depth = request.headers.getHeader("depth", "0")
-    if depth != "0":
-        log.err("Error in prinicpal-property-search REPORT, Depth set to %s" % (depth,))
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
-    
-    # Get a single DAV:prop element from the REPORT request body
-    propertiesForResource = None
-    propElement = None
-    propertySearches = []
-    applyTo = False
-    for child in principal_property_search.children:
-        if child.qname() == (dav_namespace, "prop"):
-            propertiesForResource = prop_common.propertyListForResource
-            propElement = child
-        elif child.qname() == (dav_namespace, "apply-to-principal-collection-set"):
-            applyTo = True
-        elif child.qname() == (dav_namespace, "property-search"):
-            props = child.childOfType(davxml.PropertyContainer)
-            props.removeWhitespaceNodes()
-            match = child.childOfType(davxml.Match)
-            propertySearches.append((props.children, str(match).lower()))
-    
-    def nodeMatch(node, match):
-        """
-        See if the content of the supplied node matches the supplied text.
-        Try to follow the matching guidance in rfc3744 section 9.4.1.
-        @param prop:  the property element to match.
-        @param match: the text to match against.
-        @return:      True if the property matches, False otherwise.
-        """
-        node.removeWhitespaceNodes()
-        for child in node.children:
-            if isinstance(child, davxml.PCDATAElement):
-                comp = str(child).lower()
-                if comp.find(match) != -1:
-                    return True
-            else:
-                return nodeMatch(child, match)
-        else:
-            return False
-        
-    def propertySearch(resource, request):
-        """
-        Test the resource to see if it contains properties matching the
-        property-search specification in this report.
-        @param resource: the L{DAVFile} for the resource to test.
-        @param request:  the current request.
-        @return:         True if the resource has matching properties, False otherwise.
-        """
-        for props, match in propertySearches:
-            # Test each property
-            for prop in props:
-                try:
-                    propvalue = waitForDeferred(resource.readProperty(prop.qname(), request))
-                    yield propvalue
-                    propvalue = propvalue.getResult()
-                    if propvalue and not nodeMatch(propvalue, match):
-                        yield False
-                        return
-                except HTTPError:
-                    # No property => no match
-                    yield False
-                    return
-        
-        yield True
-
-    propertySearch = deferredGenerator(propertySearch)
-
-    # Run report
-    try:
-        resources = []
-        responses = []
-        matchcount = 0
-
-        if applyTo:
-            for principalCollection in self.principalCollections():
-                uri = principalCollection.principalCollectionURL()
-                resource = waitForDeferred(request.locateResource(uri))
-                yield resource
-                resource = resource.getResult()
-                if resource:
-                    resources.append((resource, uri))
-        else:
-            resources.append((self, request.uri))
-
-        # Loop over all collections and principal resources within
-        for resource, ruri in resources:
-
-            # 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(resource.inheritedACEsforChildren(request))
-            yield filteredaces
-            filteredaces = filteredaces.getResult()
-
-            children = []
-            d = waitForDeferred(resource.findChildren("infinity", request, lambda x, y: children.append((x,y)),
-                                                      privileges=(davxml.Read(),), inherited_aces=filteredaces))
-            yield d
-            d.getResult()
-
-            for child, uri in children:
-                if isPrincipalResource(child):
-                    d = waitForDeferred(propertySearch(child, request))
-                    yield d
-                    d = d.getResult()
-                    if d:
-                        # 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 NumberOfMatchesWithinLimits:
-        log.err("Too many matching components in prinicpal-property-search report")
-        raise HTTPError(ErrorResponse(
-            responsecode.FORBIDDEN,
-            davxml.NumberOfMatchesWithinLimits()
-        ))
-
-    yield MultiStatusResponse(responses)
-
-report_DAV__principal_property_search = deferredGenerator(report_DAV__principal_property_search)

Copied: CalendarServer/trunk/twext/web2/dav/method/report_principal_property_search.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/report_principal_property_search.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/report_principal_property_search.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/report_principal_property_search.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,186 @@
+# -*- test-case-name: twext.web2.dav.test.test_report_expand -*-
+##
+# Copyright (c) 2006 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
+##
+
+"""
+WebDAV prinicpal-property-search report
+"""
+
+__all__ = ["report_DAV__principal_property_search"]
+
+from twisted.python import log
+from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2.dav import davxml
+from twext.web2.dav.davxml import dav_namespace
+from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
+from twext.web2.dav.method import prop_common
+from twext.web2.dav.method.report import NumberOfMatchesWithinLimits
+from twext.web2.dav.method.report import max_number_of_matches
+from twext.web2.dav.resource import isPrincipalResource
+
+def report_DAV__principal_property_search(self, request, principal_property_search):
+    """
+    Generate a principal-property-search REPORT. (RFC 3744, section 9.4)
+    """
+
+    # Verify root element
+    if not isinstance(principal_property_search, davxml.PrincipalPropertySearch):
+        raise ValueError("%s expected as root element, not %s."
+                         % (davxml.PrincipalPropertySearch.sname(), principal_property_search.sname()))
+
+    # Only handle Depth: 0
+    depth = request.headers.getHeader("depth", "0")
+    if depth != "0":
+        log.err("Error in prinicpal-property-search REPORT, Depth set to %s" % (depth,))
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
+    
+    # Get a single DAV:prop element from the REPORT request body
+    propertiesForResource = None
+    propElement = None
+    propertySearches = []
+    applyTo = False
+    for child in principal_property_search.children:
+        if child.qname() == (dav_namespace, "prop"):
+            propertiesForResource = prop_common.propertyListForResource
+            propElement = child
+        elif child.qname() == (dav_namespace, "apply-to-principal-collection-set"):
+            applyTo = True
+        elif child.qname() == (dav_namespace, "property-search"):
+            props = child.childOfType(davxml.PropertyContainer)
+            props.removeWhitespaceNodes()
+            match = child.childOfType(davxml.Match)
+            propertySearches.append((props.children, str(match).lower()))
+    
+    def nodeMatch(node, match):
+        """
+        See if the content of the supplied node matches the supplied text.
+        Try to follow the matching guidance in rfc3744 section 9.4.1.
+        @param prop:  the property element to match.
+        @param match: the text to match against.
+        @return:      True if the property matches, False otherwise.
+        """
+        node.removeWhitespaceNodes()
+        for child in node.children:
+            if isinstance(child, davxml.PCDATAElement):
+                comp = str(child).lower()
+                if comp.find(match) != -1:
+                    return True
+            else:
+                return nodeMatch(child, match)
+        else:
+            return False
+        
+    def propertySearch(resource, request):
+        """
+        Test the resource to see if it contains properties matching the
+        property-search specification in this report.
+        @param resource: the L{DAVFile} for the resource to test.
+        @param request:  the current request.
+        @return:         True if the resource has matching properties, False otherwise.
+        """
+        for props, match in propertySearches:
+            # Test each property
+            for prop in props:
+                try:
+                    propvalue = waitForDeferred(resource.readProperty(prop.qname(), request))
+                    yield propvalue
+                    propvalue = propvalue.getResult()
+                    if propvalue and not nodeMatch(propvalue, match):
+                        yield False
+                        return
+                except HTTPError:
+                    # No property => no match
+                    yield False
+                    return
+        
+        yield True
+
+    propertySearch = deferredGenerator(propertySearch)
+
+    # Run report
+    try:
+        resources = []
+        responses = []
+        matchcount = 0
+
+        if applyTo:
+            for principalCollection in self.principalCollections():
+                uri = principalCollection.principalCollectionURL()
+                resource = waitForDeferred(request.locateResource(uri))
+                yield resource
+                resource = resource.getResult()
+                if resource:
+                    resources.append((resource, uri))
+        else:
+            resources.append((self, request.uri))
+
+        # Loop over all collections and principal resources within
+        for resource, ruri in resources:
+
+            # 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(resource.inheritedACEsforChildren(request))
+            yield filteredaces
+            filteredaces = filteredaces.getResult()
+
+            children = []
+            d = waitForDeferred(resource.findChildren("infinity", request, lambda x, y: children.append((x,y)),
+                                                      privileges=(davxml.Read(),), inherited_aces=filteredaces))
+            yield d
+            d.getResult()
+
+            for child, uri in children:
+                if isPrincipalResource(child):
+                    d = waitForDeferred(propertySearch(child, request))
+                    yield d
+                    d = d.getResult()
+                    if d:
+                        # 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 NumberOfMatchesWithinLimits:
+        log.err("Too many matching components in prinicpal-property-search report")
+        raise HTTPError(ErrorResponse(
+            responsecode.FORBIDDEN,
+            davxml.NumberOfMatchesWithinLimits()
+        ))
+
+    yield MultiStatusResponse(responses)
+
+report_DAV__principal_property_search = deferredGenerator(report_DAV__principal_property_search)

Deleted: CalendarServer/trunk/twext/web2/dav/method/report_principal_search_property_set.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/report_principal_search_property_set.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/method/report_principal_search_property_set.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,62 +0,0 @@
-# -*- test-case-name: twext.web2.dav.test.test_report_expand -*-
-##
-# Copyright (c) 2006 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
-##
-
-"""
-WebDAV principal-search-property-set report
-"""
-
-__all__ = ["report_DAV__principal_search_property_set"]
-
-from twisted.internet.defer import deferredGenerator
-from twisted.python import log
-from twext.web2 import responsecode
-from twext.web2.dav import davxml
-from twext.web2.http import HTTPError, Response, StatusResponse
-from twext.web2.stream import MemoryStream
-
-def report_DAV__principal_search_property_set(self, request, principal_search_property_set):
-    """
-    Generate a principal-search-property-set REPORT. (RFC 3744, section 9.5)
-    """
-    # Verify root element
-    if not isinstance(principal_search_property_set, davxml.PrincipalSearchPropertySet):
-        raise ValueError("%s expected as root element, not %s."
-                         % (davxml.PrincipalSearchPropertySet.sname(), principal_search_property_set.sname()))
-
-    # Only handle Depth: 0
-    depth = request.headers.getHeader("depth", "0")
-    if depth != "0":
-        log.err("Error in principal-search-property-set REPORT, Depth set to %s" % (depth,))
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
-    
-    # Get details from the resource
-    result = self.principalSearchPropertySet()
-    if result is None:
-        log.err("Error in principal-search-property-set REPORT not supported on: %s" % (self,))
-        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Not allowed on this resource"))
-        
-    yield Response(code=responsecode.OK, stream=MemoryStream(result.toxml()))
-
-report_DAV__principal_search_property_set = deferredGenerator(report_DAV__principal_search_property_set)

Copied: CalendarServer/trunk/twext/web2/dav/method/report_principal_search_property_set.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/method/report_principal_search_property_set.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/report_principal_search_property_set.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/method/report_principal_search_property_set.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,62 @@
+# -*- test-case-name: twext.web2.dav.test.test_report_expand -*-
+##
+# Copyright (c) 2006 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
+##
+
+"""
+WebDAV principal-search-property-set report
+"""
+
+__all__ = ["report_DAV__principal_search_property_set"]
+
+from twisted.internet.defer import deferredGenerator
+from twisted.python import log
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.http import HTTPError, Response, StatusResponse
+from twext.web2.stream import MemoryStream
+
+def report_DAV__principal_search_property_set(self, request, principal_search_property_set):
+    """
+    Generate a principal-search-property-set REPORT. (RFC 3744, section 9.5)
+    """
+    # Verify root element
+    if not isinstance(principal_search_property_set, davxml.PrincipalSearchPropertySet):
+        raise ValueError("%s expected as root element, not %s."
+                         % (davxml.PrincipalSearchPropertySet.sname(), principal_search_property_set.sname()))
+
+    # Only handle Depth: 0
+    depth = request.headers.getHeader("depth", "0")
+    if depth != "0":
+        log.err("Error in principal-search-property-set REPORT, Depth set to %s" % (depth,))
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
+    
+    # Get details from the resource
+    result = self.principalSearchPropertySet()
+    if result is None:
+        log.err("Error in principal-search-property-set REPORT not supported on: %s" % (self,))
+        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Not allowed on this resource"))
+        
+    yield Response(code=responsecode.OK, stream=MemoryStream(result.toxml()))
+
+report_DAV__principal_search_property_set = deferredGenerator(report_DAV__principal_search_property_set)

Copied: CalendarServer/trunk/twext/web2/dav/noneprops.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/noneprops.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/noneprops.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/noneprops.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,67 @@
+##
+# 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
+##
+
+"""
+Empty DAV property store.
+
+This API is considered private to static.py and is therefore subject to
+change.
+"""
+
+__all__ = ["NonePropertyStore"]
+
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError, StatusResponse
+
+class NonePropertyStore (object):
+    """
+    DAV property store which contains no properties and does not allow
+    properties to be set.
+    """
+    __singleton = None
+
+    def __new__(clazz, resource):
+        if NonePropertyStore.__singleton is None:
+            NonePropertyStore.__singleton = object.__new__(clazz)
+        return NonePropertyStore.__singleton
+
+    def __init__(self, resource):
+        pass
+
+    def get(self, qname):
+        raise HTTPError(StatusResponse(responsecode.NOT_FOUND, "No such property: {%s}%s" % qname))
+
+    def set(self, property):
+        raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Permission denied for setting property: %s" % (property,)))
+
+    def delete(self, qname):
+        # RFC 2518 Section 12.13.1 says that removal of
+        # non-existing property is not an error.
+        pass
+
+    def contains(self, qname):
+        return False
+
+    def list(self):
+        return ()

Copied: CalendarServer/trunk/twext/web2/dav/resource.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/resource.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/resource.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/resource.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,2270 @@
+# -*- test-case-name: twext.web2.dav.test.test_resource -*-
+##
+# 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
+##
+
+"""
+WebDAV resources.
+"""
+
+__all__ = [
+    "DAVPropertyMixIn",
+    "DAVResource",
+    "DAVLeafResource",
+    "DAVPrincipalResource",
+    "DAVPrincipalCollectionResource",
+    "AccessDeniedError",
+    "isPrincipalResource",
+    "TwistedACLInheritable",
+    "TwistedGETContentMD5",
+    "TwistedQuotaRootProperty",
+    "allACL",
+    "readonlyACL",
+    "davPrivilegeSet",
+    "unauthenticatedPrincipal",
+]
+
+import __builtin__
+if not hasattr(__builtin__, "set"):
+    import sets.Set as set
+if not hasattr(__builtin__, "frozenset"):
+    import sets.ImmutableSet as frozenset
+
+import urllib
+
+from zope.interface import implements
+from twisted.cred.error import LoginFailed, UnauthorizedLogin
+from twisted.python import log
+from twisted.python.failure import Failure
+from twisted.internet.defer import Deferred, maybeDeferred, succeed, inlineCallbacks
+from twisted.internet.defer import waitForDeferred, deferredGenerator
+from twisted.internet import reactor
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError, RedirectResponse, StatusResponse
+from twext.web2.http_headers import generateContentType
+from twext.web2.iweb import IResponse
+from twext.web2.resource import LeafResource
+from twext.web2.server import NoURLForResourceError
+from twext.web2.static import MetaDataMixin, StaticRenderMixin
+from twext.web2.auth.wrapper import UnauthorizedResponse
+from twext.web2.dav import davxml
+from twext.web2.dav.davxml import dav_namespace, lookupElement
+from twext.web2.dav.davxml import twisted_dav_namespace, twisted_private_namespace
+from twext.web2.dav.idav import IDAVResource, IDAVPrincipalResource, IDAVPrincipalCollectionResource
+from twext.web2.dav.http import NeedPrivilegesResponse
+from twext.web2.dav.noneprops import NonePropertyStore
+from twext.web2.dav.util import unimplemented, parentForURL, joinURL
+from twext.web2.dav.auth import PrincipalCredentials
+
+class DAVPropertyMixIn (MetaDataMixin):
+    """
+    Mix-in class which implements the DAV property access API in
+    L{IDAVResource}.
+
+    There are three categories of DAV properties, for the purposes of how this
+    class manages them.  A X{property} is either a X{live property} or a
+    X{dead property}, and live properties are split into two categories:
+
+     1. Dead properties.  There are properties that the server simply stores as
+        opaque data.  These are store in the X{dead property store}, which is
+        provided by subclasses via the L{deadProperties} method.
+
+     2. Live properties which are always computed.  These properties aren't
+        stored anywhere (by this class) but instead are derived from the resource
+        state or from data that is persisted elsewhere.  These are listed in the
+        L{liveProperties} attribute and are handled explicitly by the
+        L{readProperty} method.
+
+     3. Live properties may be acted on specially and are stored in the X{dead
+        property store}.  These are not listed in the L{liveProperties} attribute,
+        but may be handled specially by the property access methods.  For
+        example, L{writeProperty} might validate the data and refuse to write
+        data it deems inappropriate for a given property.
+
+    There are two sets of property access methods.  The first group
+    (L{hasProperty}, etc.) provides access to all properties.  They
+    automatically figure out which category a property falls into and act
+    accordingly.
+
+    The second group (L{hasDeadProperty}, etc.) accesses the dead property store
+    directly and bypasses any live property logic that exists in the first group
+    of methods.  These methods are used by the first group of methods, and there
+    are cases where they may be needed by other methods.  I{Accessing dead
+    properties directly should be done with caution.}  Bypassing the live
+    property logic means that values may not be the correct ones for use in
+    DAV requests such as PROPFIND, and may be bypassing security checks.  In
+    general, one should never bypass the live property logic as part of a client
+    request for property data.
+
+    Properties in the L{twisted_private_namespace} namespace are internal to the
+    server and should not be exposed to clients.  They can only be accessed via
+    the dead property store.
+    """
+    # Note:
+    #  The DAV:owner and DAV:group live properties are only meaningful if you
+    # are using ACL semantics (ie. Unix-like) which use them.  This (generic)
+    # class does not.
+
+    liveProperties = (
+        (dav_namespace, "resourcetype"              ),
+        (dav_namespace, "getetag"                   ),
+        (dav_namespace, "getcontenttype"            ),
+        (dav_namespace, "getcontentlength"          ),
+        (dav_namespace, "getlastmodified"           ),
+        (dav_namespace, "creationdate"              ),
+        (dav_namespace, "displayname"               ),
+        (dav_namespace, "supportedlock"             ),
+        (dav_namespace, "supported-report-set"      ), # RFC 3253, section 3.1.5
+       #(dav_namespace, "owner"                     ), # RFC 3744, section 5.1
+       #(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"),
+    )
+
+    def deadProperties(self):
+        """
+        Provides internal access to the WebDAV dead property store.  You
+        probably shouldn't be calling this directly if you can use the property
+        accessors in the L{IDAVResource} API instead.  However, a subclass must
+        override this method to provide it's own dead property store.
+
+        This implementation returns an instance of L{NonePropertyStore}, which
+        cannot store dead properties.  Subclasses must override this method if
+        they wish to store dead properties.
+
+        @return: a dict-like object from which one can read and to which one can
+            write dead properties.  Keys are qname tuples (ie. C{(namespace, name)})
+            as returned by L{davxml.WebDAVElement.qname()} and values are
+            L{davxml.WebDAVElement} instances.
+        """
+        if not hasattr(self, "_dead_properties"):
+            self._dead_properties = NonePropertyStore(self)
+        return self._dead_properties
+
+    def hasProperty(self, property, request):
+        """
+        See L{IDAVResource.hasProperty}.
+        """
+        if type(property) is tuple:
+            qname = property
+        else:
+            qname = property.qname()
+
+        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):
+        """
+        See L{IDAVResource.readProperty}.
+        """
+        def defer():
+            if type(property) is tuple:
+                qname = property
+                sname = "{%s}%s" % property
+            else:
+                qname = property.qname()
+                sname = property.sname()
+
+            namespace, name = qname
+
+            if namespace == dav_namespace:
+                if name == "resourcetype":
+                    # Allow live property to be overriden by dead property
+                    if self.deadProperties().contains(qname):
+                        return self.deadProperties().get(qname)
+                    if self.isCollection():
+                        return davxml.ResourceType.collection
+                    return davxml.ResourceType.empty
+
+                if name == "getetag":
+                    etag = self.etag()
+                    if etag is None:
+                        return None
+                    return davxml.GETETag(etag.generate())
+
+                if name == "getcontenttype":
+                    mimeType = self.contentType()
+                    if mimeType is None:
+                        return None
+                    return davxml.GETContentType(generateContentType(mimeType))
+
+                if name == "getcontentlength":
+                    length = self.contentLength()
+                    if length is None:
+                        # TODO: really we should "render" the resource and 
+                        # determine its size from that but for now we just 
+                        # return an empty element.
+                        return davxml.GETContentLength("")
+                    else:
+                        return davxml.GETContentLength(str(length))
+
+                if name == "getlastmodified":
+                    lastModified = self.lastModified()
+                    if lastModified is None:
+                        return None
+                    return davxml.GETLastModified.fromDate(lastModified)
+
+                if name == "creationdate":
+                    creationDate = self.creationDate()
+                    if creationDate is None:
+                        return None
+                    return davxml.CreationDate.fromDate(creationDate)
+
+                if name == "displayname":
+                    displayName = self.displayName()
+                    if displayName is None:
+                        return None
+                    return davxml.DisplayName(displayName)
+
+                if name == "supportedlock":
+                    return davxml.SupportedLock(
+                        davxml.LockEntry(davxml.LockScope.exclusive, davxml.LockType.write),
+                        davxml.LockEntry(davxml.LockScope.shared   , davxml.LockType.write),
+                    )
+
+                if name == "supported-report-set":
+                    return davxml.SupportedReportSet(*[
+                        davxml.SupportedReport(report,)
+                        for report in self.supportedReports()
+                    ])
+
+                if name == "supported-privilege-set":
+                    return self.supportedPrivileges(request)
+
+                if name == "acl-restrictions":
+                    return davxml.ACLRestrictions()
+
+                if name == "inherited-acl-set":
+                    return davxml.InheritedACLSet(*self.inheritedACLSet())
+
+                if name == "principal-collection-set":
+                    return davxml.PrincipalCollectionSet(*[
+                        davxml.HRef(principalCollection.principalCollectionURL())
+                        for principalCollection in self.principalCollections()
+                    ])
+
+                def ifAllowed(privileges, callback):
+                    def onError(failure):
+                        failure.trap(AccessDeniedError)
+                        
+                        raise HTTPError(StatusResponse(
+                            responsecode.UNAUTHORIZED,
+                            "Access denied while reading property %s." % (sname,)
+                        ))
+
+                    d = self.checkPrivileges(request, privileges)
+                    d.addCallbacks(lambda _: callback(), onError)
+                    return d
+
+                if name == "current-user-privilege-set":
+                    def callback():
+                        d = self.currentPrivileges(request)
+                        d.addCallback(lambda privs: davxml.CurrentUserPrivilegeSet(*privs))
+                        return d
+                    return ifAllowed((davxml.ReadCurrentUserPrivilegeSet(),), callback)
+
+                if name == "acl":
+                    def callback():
+                        def gotACL(acl):
+                            if acl is None:
+                                acl = davxml.ACL()
+                            return acl
+                        d = self.accessControlList(request)
+                        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):
+                        namespace = twisted_dav_namespace
+                        name = "resource-class"
+                        hidden = False
+                    return ResourceClass(self.__class__.__name__)
+
+            elif namespace == twisted_private_namespace:
+                raise HTTPError(StatusResponse(
+                    responsecode.FORBIDDEN,
+                    "Properties in the %s namespace are private to the server." % (sname,)
+                ))
+
+            return self.deadProperties().get(qname)
+
+        return maybeDeferred(defer)
+
+    def writeProperty(self, property, request):
+        """
+        See L{IDAVResource.writeProperty}.
+        """
+        assert isinstance(property, davxml.WebDAVElement), "Not a property: %r" % (property,)
+
+        def defer():
+            if property.protected:
+                raise HTTPError(StatusResponse(
+                    responsecode.FORBIDDEN,
+                    "Protected property %s may not be set." % (property.sname(),)
+                ))
+
+            if property.namespace == twisted_private_namespace:
+                raise HTTPError(StatusResponse(
+                    responsecode.FORBIDDEN,
+                    "Properties in the %s namespace are private to the server." % (property.sname(),)
+                ))
+
+            return self.deadProperties().set(property)
+
+        return maybeDeferred(defer)
+
+    def removeProperty(self, property, request):
+        """
+        See L{IDAVResource.removeProperty}.
+        """
+        def defer():
+            if type(property) is tuple:
+                qname = property
+                sname = "{%s}%s" % property
+            else:
+                qname = property.qname()
+                sname = property.sname()
+
+            if qname in self.liveProperties:
+                raise HTTPError(StatusResponse(
+                    responsecode.FORBIDDEN,
+                    "Live property %s cannot be deleted." % (sname,)
+                ))
+
+            if qname[0] == twisted_private_namespace:
+                raise HTTPError(StatusResponse(
+                    responsecode.FORBIDDEN,
+                    "Properties in the %s namespace are private to the server." % (qname[0],)
+                ))
+
+            return self.deadProperties().delete(qname)
+
+        return maybeDeferred(defer)
+
+    def listProperties(self, request):
+        """
+        See L{IDAVResource.listProperties}.
+        """
+        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.add(qname)
+
+        yield qnames
+
+    listProperties = deferredGenerator(listProperties)
+
+    def listAllprop(self, request):
+        """
+        Some DAV properties should not be returned to a C{DAV:allprop} query.
+        RFC 3253 defines several such properties.  This method computes a subset
+        of the property qnames returned by L{listProperties} by filtering out
+        elements whose class have the C{.hidden} attribute set to C{True}.
+        @return: a list of qnames of properties which are defined and are
+            appropriate for use in response to a C{DAV:allprop} query.   
+        """
+        def doList(qnames):
+            result = []
+
+            for qname in qnames:
+                try:
+                    if not lookupElement(qname).hidden:
+                        result.append(qname)
+                except KeyError:
+                    # Unknown element
+                    result.append(qname)
+
+            return result
+
+        d = self.listProperties(request)
+        d.addCallback(doList)
+        return d
+
+    def hasDeadProperty(self, property):
+        """
+        Same as L{hasProperty}, but bypasses the live property store and checks
+        directly from the dead property store.
+        """
+        if type(property) is tuple:
+            qname = property
+        else:
+            qname = property.qname()
+
+        return self.deadProperties().contains(qname)
+
+    def readDeadProperty(self, property):
+        """
+        Same as L{readProperty}, but bypasses the live property store and reads
+        directly from the dead property store.
+        """
+        if type(property) is tuple:
+            qname = property
+        else:
+            qname = property.qname()
+
+        return self.deadProperties().get(qname)
+
+    def writeDeadProperty(self, property):
+        """
+        Same as L{writeProperty}, but bypasses the live property store and
+        writes directly to the dead property store.
+        Note that this should not be used unless you know that you are writing
+        to an overrideable live property, as this bypasses the logic which
+        protects protected properties.  The result of writing to a
+        non-overrideable live property with this method is undefined; the value
+        in the dead property store may or may not be ignored when reading the
+        property with L{readProperty}.
+        """
+        self.deadProperties().set(property)
+
+    def removeDeadProperty(self, property):
+        """
+        Same as L{removeProperty}, but bypasses the live property store and acts
+        directly on the dead property store.
+        """
+        if self.hasDeadProperty(property):
+            if type(property) is tuple:
+                qname = property
+            else:
+                qname = property.qname()
+
+            self.deadProperties().delete(qname)
+
+    #
+    # Overrides some methods in MetaDataMixin in order to allow DAV properties
+    # to override the values of some HTTP metadata.
+    #
+    def contentType(self):
+        if self.hasDeadProperty((davxml.dav_namespace, "getcontenttype")):
+            return self.readDeadProperty((davxml.dav_namespace, "getcontenttype")).mimeType()
+        else:
+            return super(DAVPropertyMixIn, self).contentType()
+
+    def displayName(self):
+        if self.hasDeadProperty((davxml.dav_namespace, "displayname")):
+            return str(self.readDeadProperty((davxml.dav_namespace, "displayname")))
+        else:
+            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
+    ##
+
+    def davComplianceClasses(self):
+        """
+        This implementation raises L{NotImplementedError}.
+        @return: a sequence of strings denoting WebDAV compliance classes.  For
+            example, a DAV level 2 server might return ("1", "2").
+        """
+        unimplemented(self)
+
+    def isCollection(self):
+        """
+        See L{IDAVResource.isCollection}.
+
+        This implementation raises L{NotImplementedError}; a subclass must
+        override this method.
+        """
+        unimplemented(self)
+
+    def findChildren(self, depth, request, callback, privileges=None, inherited_aces=None):
+        """
+        See L{IDAVResource.findChildren}.
+
+        This implementation works for C{depth} values of C{"0"}, C{"1"}, 
+        and C{"infinity"}.  As long as C{self.listChildren} is implemented
+        """
+        assert depth in ("0", "1", "infinity"), "Invalid depth: %s" % (depth,)
+
+        if depth == "0" or not self.isCollection():
+            return succeed(None)
+
+        completionDeferred = Deferred()
+        basepath = request.urlForResource(self)
+        children = list(self.listChildren())
+
+        def checkPrivilegesError(failure):
+            failure.trap(AccessDeniedError)
+            reactor.callLater(0, getChild)
+
+        def checkPrivileges(child):
+            if child is None:
+                return None
+
+            if privileges is None:
+                return child
+   
+            d = child.checkPrivileges(request, privileges, inherited_aces=inherited_aces)
+            d.addCallback(lambda _: child)
+            return d
+
+        def gotChild(child, childpath):
+            if child is None:
+                callback(None, childpath + "/")
+            else:
+                if child.isCollection():
+                    callback(child, childpath + "/")
+                    if depth == "infinity":
+                        d = child.findChildren(depth, request, callback, privileges)
+                        d.addCallback(lambda x: reactor.callLater(0, getChild))
+                        return d
+                else:
+                    callback(child, childpath)
+
+            reactor.callLater(0, getChild)
+
+        def getChild():
+            try:
+                childname = children.pop()
+            except IndexError:
+                completionDeferred.callback(None)
+            else:
+                childpath = joinURL(basepath, childname)
+                d = request.locateChildResource(self, childname)
+                d.addCallback(checkPrivileges)
+                d.addCallbacks(gotChild, checkPrivilegesError, (childpath,))
+                d.addErrback(completionDeferred.errback)
+
+        getChild()
+
+        return completionDeferred
+
+    def supportedReports(self):
+        """
+        See L{IDAVResource.supportedReports}.
+        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
+    ##
+
+    def authorize(self, request, privileges, recurse=False):
+        """
+        See L{IDAVResource.authorize}.
+        """
+        d = self.authenticate(request)
+        def whenAuthenticated(result):
+            privilegeCheck = self.checkPrivileges(request, privileges, recurse)
+            return privilegeCheck.addErrback(whenAccessDenied)
+        def whenAccessDenied(f):
+            f.trap(AccessDeniedError)
+
+            # 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.
+
+            # 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
+            def translateError(response):
+                return Failure(HTTPError(response))
+            if request.authnUser == davxml.Principal(davxml.Unauthenticated()):
+                return UnauthorizedResponse.makeResponse(
+                    request.credentialFactories,
+                    request.remoteAddr).addCallback(translateError)
+            else:
+                return translateError(
+                    NeedPrivilegesResponse(request.uri, f.value.errors))
+        d.addCallback(whenAuthenticated)
+        return d
+
+
+    def authenticate(self, request):
+        """
+        Authenticate the given request against the portal, setting both
+        C{request.authzUser} (a C{str}, the username for the purposes of
+        authorization) and C{request.authnUser} (a C{str}, the username for the
+        purposes of authentication) when it has been authenticated.
+
+        In order to authenticate, the request must have been previously
+        prepared by L{twext.web2.dav.auth.AuthenticationWrapper.hook} to have
+        the necessary authentication metadata.
+
+        If the request was not thusly prepared, both C{authzUser} and
+        C{authnUser} will be L{davxml.Unauthenticated}.
+
+        @param request: the request which may contain authentication
+            information and a reference to a portal to authenticate against.
+
+        @type request: L{twext.web2.iweb.IRequest}.
+
+        @return: a L{Deferred} which fires with a 2-tuple of C{(authnUser,
+            authzUser)} if either the request is unauthenticated OR contains
+            valid credentials to authenticate as a principal, or errbacks with
+            L{HTTPError} if the authentication scheme is unsupported, or the
+            credentials provided by the request are not valid.
+        """
+        if not (hasattr(request, 'portal') and
+                hasattr(request, 'credentialFactories') and
+                hasattr(request, 'loginInterfaces')):
+            request.authnUser = davxml.Principal(davxml.Unauthenticated())
+            request.authzUser = davxml.Principal(davxml.Unauthenticated())
+            return succeed((request.authnUser, request.authzUser))
+
+        authHeader = request.headers.getHeader('authorization')
+
+        if authHeader is not None:
+            if authHeader[0] not in request.credentialFactories:
+                log.err("Client authentication scheme %s is not provided by server %s"
+                        % (authHeader[0], request.credentialFactories.keys()))
+                d = UnauthorizedResponse.makeResponse(
+                    request.credentialFactories,
+                    request.remoteAddr)
+                def _fail(response):
+                    return Failure(HTTPError(response))
+                return d.addCallback(_fail)
+            else:
+                factory = request.credentialFactories[authHeader[0]]
+
+                d = factory.decode(authHeader[1], request)
+
+                def gotCreds(creds):
+                    return self.principalsForAuthID(
+                        request, creds.username
+                        ).addCallback(gotDetails, creds)
+                # Try to match principals in each principal collection on the resource
+                def gotDetails(details, creds):
+                    authnPrincipal = IDAVPrincipalResource(details[0])
+                    authzPrincipal = IDAVPrincipalResource(details[1])
+                    return PrincipalCredentials(authnPrincipal, authzPrincipal, creds)
+
+                def login(pcreds):
+                    return request.portal.login(
+                        pcreds, None, *request.loginInterfaces)
+                def gotAuth(result):
+                    request.authnUser = result[1]
+                    request.authzUser = result[2]
+                    return (request.authnUser, request.authzUser)
+                def translateUnauthenticated(f):
+                    f.trap(UnauthorizedLogin, LoginFailed)
+                    log.msg("Authentication failed: %s" % (f.value,))
+                    return UnauthorizedResponse.makeResponse(
+                        request.credentialFactories, request.remoteAddr).addCallback(
+                        lambda response: Failure(HTTPError(response)))
+                d.addCallback(gotCreds).addCallback(login).addCallbacks(
+                    gotAuth, translateUnauthenticated)
+                return d
+        else:
+            request.authnUser = davxml.Principal(davxml.Unauthenticated())
+            request.authzUser = davxml.Principal(davxml.Unauthenticated())
+            return succeed((request.authnUser, request.authzUser))
+
+    ##
+    # ACL
+    ##
+
+    def currentPrincipal(self, request):
+        """
+        @param request: the request being processed.
+        @return: the current authorized principal, as derived from the given request.
+        """
+        if hasattr(request, "authzUser"):
+            return request.authzUser
+        else:
+            return unauthenticatedPrincipal
+
+    def principalCollections(self):
+        """
+        See L{IDAVResource.principalCollections}.
+        """
+        if hasattr(self, "_principalCollections"):
+            return self._principalCollections
+        else:
+            return ()
+
+    def defaultRootAccessControlList(self):
+        """
+        @return: the L{davxml.ACL} element containing the default access control
+            list for this resource.
+        """
+        #
+        # The default behaviour is to allow GET access to everything
+        # and deny any type of write access (PUT, DELETE, etc.) to
+        # everything.
+        #
+        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}.
+
+        This implementation stores the ACL in the private property
+        C{(L{twisted_private_namespace}, "acl")}.
+        """
+        self.writeDeadProperty(acl)
+
+    def mergeAccessControlList(self, new_acl, request):
+        """
+        Merges the supplied access control list with the one on this resource.
+        Merging means change all the non-inherited and non-protected ace's in
+        the original, and do not allow the new one to specify an inherited or
+        protected access control entry. This is the behaviour required by the
+        C{ACL} request. (RFC 3744, section 8.1).
+        @param new_acl:  an L{davxml.ACL} element
+        @param request: the request being processed.
+        @return: a tuple of the C{DAV:error} precondition element if an error
+            occurred, C{None} otherwise.
+
+        This implementation stores the ACL in the private property
+        """
+        # C{(L{twisted_private_namespace}, "acl")}.
+        
+        # Steps for ACL evaluation:
+        #  1. Check that ace's on incoming do not match a protected ace
+        #  2. Check that ace's on incoming do not match an inherited ace
+        #  3. Check that ace's on incoming all have deny before grant
+        #  4. Check that ace's on incoming do not use abstract privilege
+        #  5. Check that ace's on incoming are supported (and are not inherited themselves)
+        #  6. Check that ace's on incoming have valid principals
+        #  7. Copy the original
+        #  8. Remove all non-inherited and non-protected - and also inherited
+        #  9. Add in ace's from incoming
+        # 10. Verify that new acl is not in conflict with itself
+        # 11. Update acl on the resource
+
+        # 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 = []
+        def addSupportedPrivilege(sp):
+            """
+            Add the element in any DAV:Privilege to our list
+            and recurse into any DAV:SupportedPrivilege's
+            """
+            for item in sp.children:
+                if isinstance(item, davxml.Privilege):
+                    supported.append(item.children[0])
+                elif isinstance(item, davxml.SupportedPrivilege):
+                    addSupportedPrivilege(item)
+
+        supportedPrivs = waitForDeferred(self.supportedPrivileges(request))
+        yield supportedPrivs
+        supportedPrivs = supportedPrivs.getResult()
+        for item in supportedPrivs.children:
+            assert isinstance(item, davxml.SupportedPrivilege), "Not a SupportedPrivilege: %r" % (item,)
+            addSupportedPrivilege(item)
+
+        # Steps 1 - 6
+        got_deny = False
+        for ace in new_acl.children:
+            for old_ace in old_acl.children:
+                if (ace.principal == old_ace.principal):
+                    # Step 1
+                    if old_ace.protected:
+                        log.err("Attempt to overwrite protected ace %r on resource %r" % (old_ace, self))
+                        yield (davxml.dav_namespace, "no-protected-ace-conflict")
+                        return
+
+                    # Step 2
+                    #
+                    # RFC3744 says that we either enforce the inherited ace
+                    # conflict or we ignore it but use access control evaluation
+                    # to determine whether there is any impact. Given that we
+                    # have the "inheritable" behavior it does not make sense to
+                    # disallow overrides of inherited ACEs since "inheritable"
+                    # cannot itself be controlled via protocol.
+                    #
+                    # Otherwise, we'd use this logic:
+                    #
+                    #elif old_ace.inherited:
+                    #    log.err("Attempt to overwrite inherited ace %r on resource %r" % (old_ace, self))
+                    #    yield (davxml.dav_namespace, "no-inherited-ace-conflict")
+                    #    return
+
+            # Step 3
+            if ace.allow and got_deny:
+                log.err("Attempt to set grant ace %r after deny ace on resource %r" % (ace, self))
+                yield (davxml.dav_namespace, "deny-before-grant")
+                return
+            got_deny = not ace.allow
+
+            # Step 4: ignore as this server has no abstract privileges (FIXME: none yet?)
+
+            # Step 5
+            for privilege in ace.privileges:
+                if privilege.children[0] not in supported:
+                    log.err("Attempt to use unsupported privilege %r in ace %r on resource %r" % (privilege.children[0], ace, self))
+                    yield (davxml.dav_namespace, "not-supported-privilege")
+                    return
+            if ace.protected:
+                log.err("Attempt to create protected ace %r on resource %r" % (ace, self))
+                yield (davxml.dav_namespace, "no-ace-conflict")
+                return
+            if ace.inherited:
+                log.err("Attempt to create inherited ace %r on resource %r" % (ace, self))
+                yield (davxml.dav_namespace, "no-ace-conflict")
+                return
+
+            # Step 6
+            valid = waitForDeferred(self.validPrincipal(ace.principal, request))
+            yield valid
+            valid = valid.getResult()
+
+            if not valid:
+                log.err("Attempt to use unrecognized principal %r in ace %r on resource %r" % (ace.principal, ace, self))
+                yield (davxml.dav_namespace, "recognized-principal")
+                return
+
+        # Step 8 & 9
+        #
+        # Iterate through the old ones and replace any that are in the new set, or remove
+        # the non-inherited/non-protected not in the new set
+        #
+        new_aces = [ace for ace in new_acl.children]
+        new_set = []
+        for old_ace in old_acl.children:
+            for i, new_ace in enumerate(new_aces):
+                if self.samePrincipal(new_ace.principal, old_ace.principal):
+                    new_set.append(new_ace)
+                    del new_aces[i]
+                    break
+            else:
+                if old_ace.protected and not old_ace.inherited:
+                    new_set.append(old_ace)
+        new_set.extend(new_aces)
+
+        # Step 10
+        # FIXME: verify acl is self-consistent
+
+        # Step 11
+        self.writeNewACEs(new_set)
+        yield None
+
+    mergeAccessControlList = deferredGenerator(mergeAccessControlList)
+        
+    def writeNewACEs(self, new_aces):
+        """
+        Write a new ACL to the resource's property store.
+        This is a separate method so that it can be overridden by
+        resources that need to do extra processing of ACLs being set
+        via the ACL command.
+        @param new_aces: C{list} of L{ACE} for ACL being set.
+        """
+        self.setAccessControlList(davxml.ACL(*new_aces))
+
+    def matchPrivilege(self, privilege, ace_privileges, supportedPrivileges):
+        for ace_privilege in ace_privileges:
+            if privilege == ace_privilege or ace_privilege.isAggregateOf(privilege, supportedPrivileges):
+                return True
+
+        return False
+
+    def checkPrivileges(self, request, privileges, recurse=False, principal=None, inherited_aces=None):
+        """
+        Check whether the given principal has the given privileges.
+        (RFC 3744, section 5.5)
+        @param request: the request being processed.
+        @param privileges: an iterable of L{davxml.WebDAVElement} elements
+            denoting access control privileges.
+        @param recurse: C{True} if a recursive check on all child
+            resources of this resource should be performed as well,
+            C{False} otherwise.
+        @param principal: the L{davxml.Principal} to check privileges
+            for.  If C{None}, it is deduced from C{request} by calling
+            L{currentPrincipal}.
+        @param inherited_aces: a list of L{davxml.ACE}s corresponding to the precomputed
+            inheritable aces from the parent resource hierarchy.
+        @return: a L{Deferred} that callbacks with C{None} or errbacks with an
+            L{AccessDeniedError}
+        """
+        if principal is None:
+            principal = self.currentPrincipal(request)
+
+        supportedPrivs = waitForDeferred(self.supportedPrivileges(request))
+        yield supportedPrivs
+        supportedPrivs = supportedPrivs.getResult()
+
+        # Other principals types don't make sense as actors.
+        assert principal.children[0].name in ("unauthenticated", "href"), (
+            "Principal is not an actor: %r" % (principal,)
+        )
+
+        errors = []
+
+        resources = [(self, None)]
+
+        if recurse:
+            x = self.findChildren("infinity", request, lambda x, y: resources.append((x,y)))
+            x = waitForDeferred(x)
+            yield x
+            x.getResult()
+
+        for resource, uri in resources:
+            acl = waitForDeferred(resource.accessControlList(request, inherited_aces=inherited_aces))
+            yield acl
+            acl = acl.getResult()
+
+            # Check for disabled
+            if acl is None:
+                errors.append((uri, list(privileges)))
+                continue
+
+            pending = list(privileges)
+            denied = []
+
+            for ace in acl.children:
+                for privilege in tuple(pending):
+                    if not self.matchPrivilege(davxml.Privilege(privilege), ace.privileges, supportedPrivs):
+                        continue
+
+                    match = waitForDeferred(self.matchPrincipal(principal, ace.principal, request))
+                    yield match
+                    match = match.getResult()
+
+                    if match:
+                        if ace.invert:
+                            continue
+                    else:
+                        if not ace.invert:
+                            continue
+
+                    pending.remove(privilege)
+
+                    if not ace.allow:
+                        denied.append(privilege)
+
+            denied += pending # If no matching ACE, then denied
+
+            if denied: 
+                errors.append((uri, denied))
+
+        if errors:
+            raise AccessDeniedError(errors,)
+        
+        yield None
+
+    checkPrivileges = deferredGenerator(checkPrivileges)
+
+    def supportedPrivileges(self, request):
+        """
+        See L{IDAVResource.supportedPrivileges}.
+
+        This implementation returns a supported privilege set containing only
+        the DAV:all privilege.
+        """
+        return succeed(allPrivilegeSet)
+
+    def currentPrivileges(self, request):
+        """
+        See L{IDAVResource.currentPrivileges}.
+
+        This implementation returns a current privilege set containing only
+        the DAV:all privilege.
+        """
+        current = self.currentPrincipal(request)
+        return self.privilegesForPrincipal(current, request)
+
+    def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
+        """
+        See L{IDAVResource.accessControlList}.
+
+        This implementation looks up the ACL in the private property
+        C{(L{twisted_private_namespace}, "acl")}.
+        If no ACL has been stored for this resource, it returns the value
+        returned by C{defaultAccessControlList}.
+        If access is disabled it will return C{None}.
+        """
+        #
+        # Inheritance is problematic. Here is what we do:
+        #
+        # 1. A private element <Twisted:inheritable> is defined for use inside
+        #    of a <DAV:ace>. This private element is removed when the ACE is
+        #    exposed via WebDAV.
+        #
+        # 2. When checking ACLs with inheritance resolution, the server must
+        #    examine all parent resources of the current one looking for any
+        #    <Twisted:inheritable> elements.
+        #
+        # If those are defined, the relevant ace is applied to the ACL on the
+        # current resource.
+        #
+        myURL = None
+
+        def getMyURL():
+            url = request.urlForResource(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, (
+                "Expected %s response from readDeadProperty() exception, not %s"
+                % (responsecode.NOT_FOUND, e.response.code)
+            )
+
+            # Produce a sensible default for an empty ACL.
+            if myURL is None:
+                myURL = getMyURL()
+
+            if myURL == "/":
+                # If we get to the root without any ACLs, then use the default.
+                acl = self.defaultRootAccessControlList()
+            else:
+                acl = self.defaultAccessControlList()
+
+        # Dynamically update privileges for those ace's that are inherited.
+        if inheritance:
+            aces = list(acl.children)
+
+            if myURL is None:
+                myURL = getMyURL()
+
+            if inherited_aces is None:
+                if myURL != "/":
+                    parentURL = parentForURL(myURL)
+    
+                    parent = waitForDeferred(request.locateResource(parentURL))
+                    yield parent
+                    parent = parent.getResult()
+    
+                    if parent:
+                        parent_acl = waitForDeferred(
+                            parent.accessControlList(request, inheritance=True, expanding=True)
+                        )
+                        yield parent_acl
+                        parent_acl = parent_acl.getResult()
+    
+                        # Check disabled
+                        if parent_acl is None:
+                            yield None
+                            return
+    
+                        for ace in parent_acl.children:
+                            if ace.inherited:
+                                aces.append(ace)
+                            elif TwistedACLInheritable() in ace.children:
+                                # Adjust ACE for inherit on this resource
+                                children = list(ace.children)
+                                children.remove(TwistedACLInheritable())
+                                children.append(davxml.Inherited(davxml.HRef(parentURL)))
+                                aces.append(davxml.ACE(*children))
+            else:
+                aces.extend(inherited_aces)
+
+            # Always filter out any remaining private properties when we are
+            # returning the ACL for the final resource after doing parent
+            # inheritance.
+            if not expanding:
+                aces = [
+                    davxml.ACE(*[
+                        c for c in ace.children
+                        if c != TwistedACLInheritable()
+                    ])
+                    for ace in aces
+                ]
+
+            acl = davxml.ACL(*aces)
+
+        yield acl
+
+    accessControlList = deferredGenerator(accessControlList)
+
+    def inheritedACEsforChildren(self, request):
+        """
+        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.
+
+        @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.
+        """
+        
+        # Get the parent ACLs with inheritance and preserve the <inheritable> element.
+        parent_acl = waitForDeferred(self.accessControlList(request, inheritance=True, expanding=True))
+        yield parent_acl
+        parent_acl = parent_acl.getResult()
+        
+        # Check disabled
+        if parent_acl is None:
+            yield None
+            return
+
+        # Filter out those that are not inheritable (and remove the inheritable element from those that are)
+        aces = []
+        for ace in parent_acl.children:
+            if ace.inherited:
+                aces.append(ace)
+            elif TwistedACLInheritable() in ace.children:
+                # Adjust ACE for inherit on this resource
+                children = list(ace.children)
+                children.remove(TwistedACLInheritable())
+                children.append(davxml.Inherited(davxml.HRef(request.urlForResource(self))))
+                aces.append(davxml.ACE(*children))
+        yield aces
+
+    inheritedACEsforChildren = deferredGenerator(inheritedACEsforChildren)
+
+    def inheritedACLSet(self):
+        """
+        @return: a sequence of L{davxml.HRef}s from which ACLs are inherited.
+
+        This implementation returns an empty set.
+        """
+        return []
+
+    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 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.
+        """
+        authnPrincipal = self.findPrincipalForAuthID(authid)
+
+        if authnPrincipal is None:
+            log.msg("Could not find the principal resource for user id: %s" % (authid,))
+            raise HTTPError(responsecode.FORBIDDEN)
+
+        d = self.authorizationPrincipal(request, authid, authnPrincipal)
+        d.addCallback(lambda authzPrincipal: (authnPrincipal, authzPrincipal))
+        return d
+
+    def findPrincipalForAuthID(self, authid):
+        """
+        Return authentication and authorization principal identifiers for the
+        authentication identifer passed in. In this implementation authn and
+        authz principals are the same.
+
+        @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
+
+    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
+        elements and data.
+        @param principal1: a L{Principal} to test.
+        @param principal2: a L{Principal} to test.
+        @return: C{True} if they are the same, C{False} otherwise.
+        """
+
+        # The interesting part of a principal is it's one child
+        principal1 = principal1.children[0]
+        principal2 = principal2.children[0]
+
+        if type(principal1) == type(principal2):
+            if isinstance(principal1, davxml.Property):
+                return type(principal1.children[0]) == type(principal2.children[0])
+            elif isinstance(principal1, davxml.HRef):
+                return str(principal1.children[0]) == str(principal2.children[0])
+            else:
+                return True
+        else:
+            return False
+                
+    def matchPrincipal(self, principal1, principal2, request):
+        """
+        Check whether the principal1 is a principal in the set defined by
+        principal2.
+        @param principal1: a L{Principal} to test. C{principal1} must contain
+            a L{davxml.HRef} or L{davxml.Unauthenticated} element.
+        @param principal2: a L{Principal} to test.
+        @param request: the request being processed.
+        @return: C{True} if they match, C{False} otherwise.
+        """
+        # See RFC 3744, section 5.5.1
+
+        principals = (principal1, principal2)
+
+        # The interesting part of a principal is it's one child
+        principal1, principal2 = [p.children[0] for p in principals]
+
+        if isinstance(principal2, davxml.All):
+            yield True
+            return
+
+        elif isinstance(principal2, davxml.Authenticated):
+            if isinstance(principal1, davxml.Unauthenticated):
+                yield False
+                return
+            elif isinstance(principal1, davxml.All):
+                yield False
+                return
+            else:
+                yield True
+                return
+
+        elif isinstance(principal2, davxml.Unauthenticated):
+            if isinstance(principal1, davxml.Unauthenticated):
+                yield True
+                return
+            else:
+                yield False
+                return
+
+        elif isinstance(principal1, davxml.Unauthenticated):
+            yield False
+            return
+
+        assert isinstance(principal1, davxml.HRef), "Not an HRef: %r" % (principal1,)
+
+        principal2 = waitForDeferred(self.resolvePrincipal(principal2, request))
+        yield principal2
+        principal2 = principal2.getResult()
+
+        assert principal2 is not None, "principal2 is None"
+
+        # Compare two HRefs and do group membership test as well
+        if principal1 == principal2:
+            yield True
+            return
+         
+        ismember = waitForDeferred(self.principalIsGroupMember(str(principal1), str(principal2), request))
+        yield ismember
+        ismember = ismember.getResult()
+
+        if ismember:
+            yield True
+            return
+  
+        yield False
+
+    matchPrincipal = deferredGenerator(matchPrincipal)
+
+    @deferredGenerator
+    def principalIsGroupMember(self, principal1, principal2, request):
+        """
+        Check whether one principal is a group member of another.
+        
+        @param principal1: C{str} principalURL for principal to test.
+        @param principal2: C{str} principalURL for possible group principal to test against.
+        @param request: the request being processed.
+        @return: L{Deferred} with result C{True} if principal1 is a member of principal2, C{False} otherwise
+        """
+        
+        d = waitForDeferred(request.locateResource(principal2))
+        yield d
+        group = d.getResult()
+
+        # 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):
+        """
+        Check whether the supplied principal is valid for this resource.
+        @param ace_principal: the L{Principal} element to test
+        @param request: the request being processed.
+        @return C{True} if C{ace_principal} is valid, C{False} otherwise.
+
+        This implementation tests for a valid element type and checks for an
+        href principal that exists inside of a principal collection.
+        """
+        def defer():
+            #
+            # We know that the element contains a valid element type, so all
+            # we need to do is check for a valid property and a valid href.
+            #
+            real_principal = ace_principal.children[0]
+
+            if isinstance(real_principal, davxml.Property):
+                # See comments in matchPrincipal().  We probably need some common code.
+                log.err("Encountered a property principal (%s), but handling is not implemented.  Invalid for ACL use."
+                        % (real_principal,))
+                return False
+
+            if isinstance(real_principal, davxml.HRef):
+                return self.validHrefPrincipal(real_principal, request)
+
+            return True
+
+        return maybeDeferred(defer)
+
+    def validHrefPrincipal(self, href_principal, request):
+        """
+        Check whether the supplied principal (in the form of an Href)
+        is valid for this resource.
+        @param href_principal: the L{Href} element to test
+        @param request: the request being processed.
+        @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 and matches the principal-URL.
+        """
+
+        # 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(_matchPrincipalURL)
+        return d
+
+    def resolvePrincipal(self, principal, request):
+        """
+        Resolves a L{davxml.Principal} element into a L{davxml.HRef} element
+        if possible.  Specifically, the given C{principal}'s contained
+        element is resolved.
+
+        L{davxml.Property} is resolved to the URI in the contained property.
+
+        L{davxml.Self} is resolved to the URI of this resource.
+
+        L{davxml.HRef} elements are returned as-is.
+
+        All other principals, including meta-principals (eg. L{davxml.All}),
+        resolve to C{None}.
+
+        @param principal: the L{davxml.Principal} child element to resolve.
+        @param request: the request being processed.
+        @return: a deferred L{davxml.HRef} element or C{None}.
+        """
+
+        if isinstance(principal, davxml.Property):
+            # raise NotImplementedError("Property principals are not implemented.")
+            #
+            # We can't raise here without potentially crippling the server in a way
+            # that can't be fixed over the wire, so let's refuse the match and log
+            # an error instead.
+            #
+            # Note: When fixing this, also fix validPrincipal()
+            #
+            log.err("Encountered a property principal (%s), but handling is not implemented; invalid for ACL use."
+                    % (principal,))
+            yield None
+            return
+
+            #
+            # FIXME: I think this is wrong - we need to get the
+            # namespace and name from the first child of DAV:property
+            #
+            namespace = principal.attributes.get(["namespace"], dav_namespace)
+            name = principal.attributes["name"]
+
+            principal = waitForDeferred(self.readProperty((namespace, name), request))
+            yield principal
+            try:
+                principal = principal.getResult()
+            except HTTPError, e:
+                assert e.response.code == responsecode.NOT_FOUND, (
+                    "Expected %s response from readProperty() exception, not %s"
+                    % (responsecode.NOT_FOUND, e.response.code)
+                )
+                yield None
+                return
+
+            if not isinstance(principal, davxml.Principal):
+                log.err("Non-principal value in property {%s}%s referenced by property principal."
+                        % (namespace, name))
+                yield None
+                return
+
+            if len(principal.children) != 1:
+                yield None
+                return
+
+            # The interesting part of a principal is it's one child
+            principal = principal.children[0]
+
+        elif isinstance(principal, davxml.Self):
+            try:
+                self = IDAVPrincipalResource(self)
+            except TypeError:
+                log.err("DAV:self ACE is set on non-principal resource %r" % (self,))
+                yield None
+                return
+            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)), (
+            "Not a meta-principal: %r" % (principal,)
+        )
+
+    resolvePrincipal = deferredGenerator(resolvePrincipal)
+
+    def privilegesForPrincipal(self, principal, request):
+        """
+        See L{IDAVResource.privilegesForPrincipal}.
+        """
+        # NB Return aggregate privileges expanded.
+
+        acl = waitForDeferred(self.accessControlList(request))
+        yield acl
+        acl = acl.getResult()
+
+        # Check disabled
+        if acl is None:
+            yield []
+
+        granted = []
+        denied = []
+        for ace in acl.children:
+            # First see if the ace's principal affects the principal being tested.
+            # FIXME: support the DAV:invert operation
+
+            match = waitForDeferred(self.matchPrincipal(principal, ace.principal, request))
+            yield match
+            match = match.getResult()
+
+            if match:
+                # Expand aggregate privileges
+                ps = []
+                supportedPrivs = waitForDeferred(self.supportedPrivileges(request))
+                yield supportedPrivs
+                supportedPrivs = supportedPrivs.getResult()
+                for p in ace.privileges:
+                    ps.extend(p.expandAggregate(supportedPrivs))
+
+                # Merge grant/deny privileges
+                if ace.allow:
+                    granted.extend([p for p in ps if p not in granted])
+                else:
+                    denied.extend([p for p in ps if p not in denied])
+
+        # Subtract denied from granted
+        allowed = [p for p in granted if p not in denied]
+
+        yield allowed
+
+    privilegesForPrincipal = deferredGenerator(privilegesForPrincipal)
+
+    def matchACEinACL(self, acl, ace):
+        """
+        Find an ACE in the ACL that matches the supplied ACE's principal.
+        @param acl: the L{ACL} to look at.
+        @param ace: the L{ACE} to try and match
+        @return:    the L{ACE} in acl that matches, None otherwise.
+        """
+        for a in acl.children:
+            if self.samePrincipal(a.principal, ace.principal):
+                return a
+        
+        return None
+    
+    def principalSearchPropertySet(self):
+        """
+        @return: a L{davxml.PrincipalSearchPropertySet} element describing the
+        principal properties that can be searched on this principal collection,
+        or C{None} if this is not a principal collection.
+        
+        This implementation returns None. Principal collection resources must
+        override and return their own suitable response.
+        """
+        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
+    ##
+
+    def renderHTTP(self, request):
+        # FIXME: This is for testing with litmus; comment out when not in use
+        #litmus = request.headers.getRawHeaders("x-litmus")
+        #if litmus: log.msg("*** Litmus test: %s ***" % (litmus,))
+
+        #
+        # 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=urllib.quote(urllib.unquote(request.path), safe=':/')+'/'))
+
+        def setHeaders(response):
+            response = IResponse(response)
+
+            response.headers.setHeader("dav", self.davComplianceClasses())
+
+            #
+            # If this is a collection and the URI doesn't end in "/", add a
+            # Content-Location header.  This is needed even if we redirect such
+            # requests (as above) in the event that this resource was created or
+            # modified by the request.
+            #
+            if self.isCollection() and request.uri[-1:] != "/":
+                response.headers.setHeader("content-location", request.uri + "/")
+
+            return response
+
+        def onError(f):
+            # If we get an HTTPError, run its response through setHeaders() as
+            # well.
+            f.trap(HTTPError)
+            return setHeaders(f.value.response)
+
+        d = maybeDeferred(super(DAVResource, self).renderHTTP, request)
+        return d.addCallbacks(setHeaders, onError)
+
+class DAVLeafResource (DAVResource, LeafResource):
+    """
+    DAV resource with no children.
+    """
+    def findChildren(self, depth, request, callback, privileges=None, inherited_aces=None):
+        return succeed(None)
+
+class DAVPrincipalResource (DAVResource):
+    """
+    Resource representing a WebDAV principal.  (RFC 3744, section 2)
+    """
+    implements(IDAVPrincipalResource)
+
+    ##
+    # WebDAV
+    ##
+
+    liveProperties = DAVResource.liveProperties + (
+        (dav_namespace, "alternate-URI-set"),
+        (dav_namespace, "principal-URL"    ),
+        (dav_namespace, "group-member-set" ),
+        (dav_namespace, "group-membership" ),
+    )
+
+    def davComplianceClasses(self):
+        return ("1", "access-control",)
+
+    def isCollection(self):
+        return False
+
+    def readProperty(self, property, request):
+        def defer():
+            if type(property) is tuple:
+                qname = property
+            else:
+                qname = property.qname()
+
+            namespace, name = qname
+
+            if namespace == dav_namespace:
+                if name == "alternate-URI-set":
+                    return davxml.AlternateURISet(*[davxml.HRef(u) for u in self.alternateURIs()])
+
+                if name == "principal-URL":
+                    return davxml.PrincipalURL(davxml.HRef(self.principalURL()))
+
+                if name == "group-member-set":
+                    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":
+                    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():
+                        return davxml.ResourceType(davxml.Collection(), davxml.Principal())
+                    else:
+                        return davxml.ResourceType(davxml.Principal())
+
+            return super(DAVPrincipalResource, self).readProperty(qname, request)
+
+        return maybeDeferred(defer)
+
+    ##
+    # ACL
+    ##
+
+    def alternateURIs(self):
+        """
+        See L{IDAVPrincipalResource.alternateURIs}.
+
+        This implementation returns C{()}.  Subclasses should override this
+        method to provide alternate URIs for this resource if appropriate.
+        """
+        return ()
+
+    def principalURL(self):
+        """
+        See L{IDAVPrincipalResource.principalURL}.
+
+        This implementation raises L{NotImplementedError}.  Subclasses must
+        override this method to provide the principal URL for this resource.
+        """
+        unimplemented(self)
+
+
+    def groupMembers(self):
+        """
+        This implementation returns a Deferred which fires with C{()}, which is
+        appropriate for non-group principals.  Subclasses should override this
+        method to provide member URLs for this resource if appropriate.
+
+        @see: L{IDAVPrincipalResource.groupMembers}.
+        """
+        return succeed(())
+
+
+    def expandedGroupMembers(self):
+        """
+        This implementation returns a Deferred which fires with C{()}, which is
+        appropriate for non-group principals.  Subclasses should override this
+        method to provide expanded member URLs for this resource if
+        appropriate.
+
+        @see: L{IDAVPrincipalResource.expandedGroupMembers}
+        """
+        return succeed(())
+
+
+    def groupMemberships(self):
+        """
+        See L{IDAVPrincipalResource.groupMemberships}.
+
+        This implementation raises L{NotImplementedError}.  Subclasses must
+        override this method to provide the group URLs for this resource.
+        """
+        unimplemented(self)
+
+    @deferredGenerator
+    def principalMatch(self, href):
+        """
+        Check whether the supplied principal matches this principal or is a
+        member of this principal resource.
+        @param href: the L{HRef} to test.
+        @return:     True if there is a match, False otherwise
+        """
+        uri = str(href)
+        if self.principalURL() == uri:
+            yield True
+            return
+        else:
+            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)
+
+    This is an abstract class; subclasses must implement C{principalForUser} in
+    order to properly implement it.
+    """
+
+    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 the URL for this principal collection.
+        """
+        return self._url
+
+
+    def principalForUser(self, user):
+        """
+        Subclasses must implement this method.
+
+        @see: L{IDAVPrincipalCollectionResource.principalForUser}
+
+        @raise: L{NotImplementedError}
+        """
+        raise NotImplementedError(
+            "%s did not implement principalForUser" % (self.__class__))
+
+
+
+class AccessDeniedError(Exception):
+    def __init__(self, errors):
+        """ 
+        An error to be raised when some request fails to meet sufficient access 
+        privileges for a resource.
+
+        @param errors: sequence of tuples, one for each resource for which one or
+            more of the given privileges are not granted, in the form
+            C{(uri, privileges)}, where uri is a URL path relative to
+            resource or C{None} if the error was in this resource,
+            privileges is a sequence of the privileges which are not
+            granted a subset thereof.
+        """
+        Exception.__init__(self, "Access denied for some resources: %r" % (errors,))
+        self.errors = errors
+
+##
+# Utilities
+##
+
+def isPrincipalResource(resource):
+    try:
+        resource = IDAVPrincipalResource(resource)
+    except TypeError:
+        return False
+    else:
+        return True
+
+class TwistedACLInheritable (davxml.WebDAVEmptyElement):
+    """
+    When set on an ACE, this indicates that the ACE privileges should be inherited by
+    all child resources within the resource with this ACE.
+    """
+    namespace = twisted_dav_namespace
+    name = "inheritable"
+
+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()),
+        davxml.Grant(davxml.Privilege(davxml.All())),
+        davxml.Protected(),
+        TwistedACLInheritable()
+    )
+)
+
+readonlyACL = davxml.ACL(
+    davxml.ACE(
+        davxml.Principal(davxml.All()),
+        davxml.Grant(davxml.Privilege(davxml.Read())),
+        davxml.Protected(),
+        TwistedACLInheritable()
+    )
+)
+
+allPrivilegeSet = davxml.SupportedPrivilegeSet(
+    davxml.SupportedPrivilege(
+        davxml.Privilege(davxml.All()),
+        davxml.Description("all privileges", **{"xml:lang": "en"})
+    )
+)
+
+#
+# This is one possible graph of the "standard" privileges documented
+# in 3744, section 3.
+#
+davPrivilegeSet = davxml.SupportedPrivilegeSet(
+    davxml.SupportedPrivilege(
+        davxml.Privilege(davxml.All()),
+        davxml.Description("all privileges", **{"xml:lang": "en"}),
+        davxml.SupportedPrivilege(
+            davxml.Privilege(davxml.Read()),
+            davxml.Description("read resource", **{"xml:lang": "en"}),
+        ),
+        davxml.SupportedPrivilege(
+            davxml.Privilege(davxml.Write()),
+            davxml.Description("write resource", **{"xml:lang": "en"}),
+            davxml.SupportedPrivilege(
+                davxml.Privilege(davxml.WriteProperties()),
+                davxml.Description("write resource properties", **{"xml:lang": "en"}),
+            ),
+            davxml.SupportedPrivilege(
+                davxml.Privilege(davxml.WriteContent()),
+                davxml.Description("write resource content", **{"xml:lang": "en"}),
+            ),
+            davxml.SupportedPrivilege(
+                davxml.Privilege(davxml.Bind()),
+                davxml.Description("add child resource", **{"xml:lang": "en"}),
+            ),
+            davxml.SupportedPrivilege(
+                davxml.Privilege(davxml.Unbind()),
+                davxml.Description("remove child resource", **{"xml:lang": "en"}),
+            ),
+        ),
+        davxml.SupportedPrivilege(
+            davxml.Privilege(davxml.Unlock()),
+            davxml.Description("unlock resource without ownership of lock", **{"xml:lang": "en"}),
+        ),
+        davxml.SupportedPrivilege(
+            davxml.Privilege(davxml.ReadACL()),
+            davxml.Description("read resource access control list", **{"xml:lang": "en"}),
+        ),
+        davxml.SupportedPrivilege(
+            davxml.Privilege(davxml.WriteACL()),
+            davxml.Description("write resource access control list", **{"xml:lang": "en"}),
+        ),
+        davxml.SupportedPrivilege(
+            davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+            davxml.Description("read privileges for current principal", **{"xml:lang": "en"}),
+        ),
+    ),
+)
+
+unauthenticatedPrincipal = davxml.Principal(davxml.Unauthenticated())

Copied: CalendarServer/trunk/twext/web2/dav/static.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/static.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/static.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/static.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,205 @@
+##
+# 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
+##
+
+"""
+WebDAV-aware static resources.
+"""
+
+__all__ = ["DAVFile"]
+
+from twisted.internet.defer import succeed, deferredGenerator, waitForDeferred
+from twisted.python.filepath import InsecurePath
+from twisted.python import log
+from twext.web2 import http_headers
+from twext.web2 import responsecode, dirlist
+from twext.web2.dav import davxml
+from twext.web2.dav.resource import DAVResource, davPrivilegeSet
+from twext.web2.dav.resource import TwistedGETContentMD5
+from twext.web2.dav.util import bindMethods
+from twext.web2.http import HTTPError, StatusResponse, RedirectResponse
+from twext.web2.static import File
+
+try:
+    from twext.web2.dav.xattrprops import xattrPropertyStore as DeadPropertyStore
+except ImportError:
+    log.msg("No dead property store available; using nonePropertyStore.")
+    log.msg("Setting of dead properties will not be allowed.")
+    from twext.web2.dav.noneprops import NonePropertyStore as DeadPropertyStore
+
+class DAVFile (DAVResource, File):
+    """
+    WebDAV-accessible File resource.
+
+    Extends twext.web2.static.File to handle WebDAV methods.
+    """
+    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
+            resource and (eg. child) resources derived from it.
+        @param indexNames: a sequence of index file names.
+        @param acl: an L{IDAVAccessControlList} with the .
+        """
+        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)
+
+    ##
+    # 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
+
+    def deadProperties(self):
+        if not hasattr(self, "_dead_properties"):
+            self._dead_properties = DeadPropertyStore(self)
+        return self._dead_properties
+
+    def isCollection(self):
+        """
+        See L{IDAVResource.isCollection}.
+        """
+        return self.fp.isdir()
+
+    ##
+    # ACL
+    ##
+
+    def supportedPrivileges(self, request):
+        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
+    ##
+
+    def ignoreExt(self, ext):
+        """
+        Does nothing; doesn't apply to this subclass.
+        """
+        pass
+
+    def locateChild(self, req, segments):
+        """
+        See L{IResource}C{.locateChild}.
+        """
+        # If getChild() finds a child resource, return it
+        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
+        # that the request wants created.
+        self.fp.restat(False)
+        if self.fp.exists() and not self.fp.isdir():
+            return (None, ())
+
+        # OK, we need to return a child corresponding to the first segment
+        path = segments[0]
+        
+        if path == "":
+            # Request is for a directory (collection) resource
+            return (self, ())
+
+        return (self.createSimilarFile(self.fp.child(path).path), segments[1:])
+
+    def createSimilarFile(self, path):
+        return self.__class__(
+            path, defaultType=self.defaultType, indexNames=self.indexNames[:],
+            principalCollections=self.principalCollections())
+
+#
+# Attach method handlers to DAVFile
+#
+
+import twext.web2.dav.method
+
+bindMethods(twext.web2.dav.method, DAVFile)

Copied: CalendarServer/trunk/twext/web2/dav/stream.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/stream.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/stream.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/stream.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,106 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+##
+# 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.
+#
+##
+
+"""
+Class that implements a stream that calculates the MD5 hash of the data
+as the data is read.
+"""
+
+__all__ = ["MD5StreamWrapper"]
+
+from twisted.python.hashlib import md5
+from twisted.internet.defer import Deferred
+from twext.web2.stream import SimpleStream
+
+
+class MD5StreamWrapper(SimpleStream):
+    """
+    An L{IByteStream} wrapper which computes the MD5 hash of the data read from
+    the wrapped stream.
+
+    @ivar _stream: The stream which is wrapped.
+    @ivar _md5: The object used to compute the running md5 hash.
+    @ivar _md5value: The hex encoded md5 hash, only set after C{close}.
+    """
+
+    def __init__(self, wrap):
+        if wrap is None:
+            raise ValueError("Stream to wrap must be provided")
+        self._stream = wrap
+        self._md5 = md5()
+
+
+    def _update(self, value):
+        """
+        Update the MD5 hash object.
+
+        @param value: L{None} or a L{str} with which to update the MD5 hash
+            object.
+
+        @return: C{value}
+        """
+        if value is not None:
+            self._md5.update(value)
+        return value
+
+
+    def read(self):
+        """
+        Read from the wrapped stream and update the MD5 hash object.
+        """
+        if self._stream is None:
+            raise RuntimeError("Cannot read after stream is closed")
+        b = self._stream.read()
+
+        if isinstance(b, Deferred):
+            b.addCallback(self._update)
+        else:
+            if b is not None:
+                self._md5.update(b)
+        return b
+
+
+    def close(self):
+        """
+        Compute the final hex digest of the contents of the wrapped stream.
+        """
+        SimpleStream.close(self)
+        self._md5value = self._md5.hexdigest()
+        self._stream = None
+        self._md5 = None
+
+
+    def getMD5(self):
+        """
+        Return the hex encoded MD5 digest of the contents of the wrapped
+        stream.  This may only be called after C{close}.
+
+        @rtype: C{str}
+        @raise RuntimeError: If C{close} has not yet been called.
+        """
+        if self._md5 is not None:
+            raise RuntimeError("Cannot get MD5 value until stream is closed")
+        return self._md5value

Modified: CalendarServer/trunk/twext/web2/dav/test/__init__.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/__init__.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/test/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,19 +1,27 @@
 ##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Computer, 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
+# 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.
 #
-# 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: Wilfredo Sanchez, wsanchez at apple.com
 ##
 
 """
-Extentions to twisted.web2.dav.test
+Tests for twext.web2.dav.
 """

Deleted: CalendarServer/trunk/twext/web2/dav/test/data/quota_100.txt
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/quota_100.txt	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/test/data/quota_100.txt	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,10 +0,0 @@
-123456789
-123456789
-123456789
-123456789
-123456789
-123456789
-123456789
-123456789
-123456789
-123456789

Copied: CalendarServer/trunk/twext/web2/dav/test/data/quota_100.txt (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/quota_100.txt)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/data/quota_100.txt	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/data/quota_100.txt	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,10 @@
+123456789
+123456789
+123456789
+123456789
+123456789
+123456789
+123456789
+123456789
+123456789
+123456789

Deleted: CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_bad.xml
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/xml/PROPFIND_bad.xml	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_bad.xml	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<propfind xmlns="DAV:">
-  <foobar/>
-  <allprop/>
-</propfind>

Copied: CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_bad.xml (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/xml/PROPFIND_bad.xml)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_bad.xml	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_bad.xml	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<propfind xmlns="DAV:">
+  <foobar/>
+  <allprop/>
+</propfind>

Deleted: CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_nonamespace.xml
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/xml/PROPFIND_nonamespace.xml	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_nonamespace.xml	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<propfind xmlns="DAV:">
-  <prop>
-    <nonamespace xmlns=""/>
-  </prop>
-</propfind>

Copied: CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_nonamespace.xml (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/xml/PROPFIND_nonamespace.xml)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_nonamespace.xml	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_nonamespace.xml	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<propfind xmlns="DAV:">
+  <prop>
+    <nonamespace xmlns=""/>
+  </prop>
+</propfind>

Deleted: CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_request.xml
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/xml/PROPFIND_request.xml	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_request.xml	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<propfind xmlns="DAV:">
-  <prop>
-    <getcontentlength xmlns="DAV:"/>
-    <getlastmodified xmlns="DAV:"/>
-    <displayname xmlns="DAV:"/>
-    <resourcetype xmlns="DAV:"/>
-    <foo xmlns="http://webdav.org/neon/litmus/"/>
-    <bar xmlns="http://webdav.org/neon/litmus/"/>
-  </prop>
-</propfind>

Copied: CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_request.xml (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/xml/PROPFIND_request.xml)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_request.xml	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_request.xml	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<propfind xmlns="DAV:">
+  <prop>
+    <getcontentlength xmlns="DAV:"/>
+    <getlastmodified xmlns="DAV:"/>
+    <displayname xmlns="DAV:"/>
+    <resourcetype xmlns="DAV:"/>
+    <foo xmlns="http://webdav.org/neon/litmus/"/>
+    <bar xmlns="http://webdav.org/neon/litmus/"/>
+  </prop>
+</propfind>

Deleted: CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_response.xml
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/xml/PROPFIND_response.xml	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_response.xml	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<D:multistatus xmlns:D="DAV:">
-  <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
-    <D:href>/uploads/</D:href>
-    <D:propstat>
-      <D:prop>
-        <lp1:resourcetype>
-          <D:collection/>
-        </lp1:resourcetype>
-        <lp1:creationdate>2005-07-05T23:08:01Z</lp1:creationdate>
-        <lp1:getlastmodified>Tue, 05 Jul 2005 23:08:01 GMT</lp1:getlastmodified>
-        <lp1:getetag>"77a99-66-27dd9640"</lp1:getetag>
-        <D:supportedlock>
-          <D:lockentry>
-            <D:lockscope>
-              <D:exclusive/>
-            </D:lockscope>
-            <D:locktype>
-              <D:write/>
-            </D:locktype>
-          </D:lockentry>
-          <D:lockentry>
-            <D:lockscope>
-              <D:shared/>
-            </D:lockscope>
-            <D:locktype>
-              <D:write/>
-            </D:locktype>
-          </D:lockentry>
-        </D:supportedlock>
-        <D:getcontenttype>httpd/unix-directory</D:getcontenttype>
-      </D:prop>
-      <D:status>HTTP/1.1 200 OK</D:status>
-    </D:propstat>
-  </D:response>
-  <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
-    <D:href>/uploads/foo.txt</D:href>
-    <D:propstat>
-      <D:prop>
-        <lp1:resourcetype/>
-        <lp1:creationdate>2005-07-05T23:08:08Z</lp1:creationdate>
-        <lp1:getcontentlength>19</lp1:getcontentlength>
-        <lp1:getlastmodified>Tue, 05 Jul 2005 23:08:08 GMT</lp1:getlastmodified>
-        <lp1:getetag>"77a9f-13-28486600"</lp1:getetag>
-        <lp2:executable>F</lp2:executable>
-        <D:supportedlock>
-          <D:lockentry>
-            <D:lockscope>
-              <D:exclusive/>
-            </D:lockscope>
-            <D:locktype>
-              <D:write/>
-            </D:locktype>
-          </D:lockentry>
-          <D:lockentry>
-            <D:lockscope>
-              <D:shared/>
-            </D:lockscope>
-            <D:locktype>
-              <D:write/>
-            </D:locktype>
-          </D:lockentry>
-        </D:supportedlock>
-        <D:getcontenttype>text/plain</D:getcontenttype>
-      </D:prop>
-      <D:status>HTTP/1.1 200 OK</D:status>
-    </D:propstat>
-  </D:response>
-</D:multistatus>

Copied: CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_response.xml (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/xml/PROPFIND_response.xml)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_response.xml	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPFIND_response.xml	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<D:multistatus xmlns:D="DAV:">
+  <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
+    <D:href>/uploads/</D:href>
+    <D:propstat>
+      <D:prop>
+        <lp1:resourcetype>
+          <D:collection/>
+        </lp1:resourcetype>
+        <lp1:creationdate>2005-07-05T23:08:01Z</lp1:creationdate>
+        <lp1:getlastmodified>Tue, 05 Jul 2005 23:08:01 GMT</lp1:getlastmodified>
+        <lp1:getetag>"77a99-66-27dd9640"</lp1:getetag>
+        <D:supportedlock>
+          <D:lockentry>
+            <D:lockscope>
+              <D:exclusive/>
+            </D:lockscope>
+            <D:locktype>
+              <D:write/>
+            </D:locktype>
+          </D:lockentry>
+          <D:lockentry>
+            <D:lockscope>
+              <D:shared/>
+            </D:lockscope>
+            <D:locktype>
+              <D:write/>
+            </D:locktype>
+          </D:lockentry>
+        </D:supportedlock>
+        <D:getcontenttype>httpd/unix-directory</D:getcontenttype>
+      </D:prop>
+      <D:status>HTTP/1.1 200 OK</D:status>
+    </D:propstat>
+  </D:response>
+  <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
+    <D:href>/uploads/foo.txt</D:href>
+    <D:propstat>
+      <D:prop>
+        <lp1:resourcetype/>
+        <lp1:creationdate>2005-07-05T23:08:08Z</lp1:creationdate>
+        <lp1:getcontentlength>19</lp1:getcontentlength>
+        <lp1:getlastmodified>Tue, 05 Jul 2005 23:08:08 GMT</lp1:getlastmodified>
+        <lp1:getetag>"77a9f-13-28486600"</lp1:getetag>
+        <lp2:executable>F</lp2:executable>
+        <D:supportedlock>
+          <D:lockentry>
+            <D:lockscope>
+              <D:exclusive/>
+            </D:lockscope>
+            <D:locktype>
+              <D:write/>
+            </D:locktype>
+          </D:lockentry>
+          <D:lockentry>
+            <D:lockscope>
+              <D:shared/>
+            </D:lockscope>
+            <D:locktype>
+              <D:write/>
+            </D:locktype>
+          </D:lockentry>
+        </D:supportedlock>
+        <D:getcontenttype>text/plain</D:getcontenttype>
+      </D:prop>
+      <D:status>HTTP/1.1 200 OK</D:status>
+    </D:propstat>
+  </D:response>
+</D:multistatus>

Deleted: CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPPATCH_request.xml
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/xml/PROPPATCH_request.xml	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPPATCH_request.xml	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,12 +0,0 @@
-<D:propertyupdate xmlns:D="DAV:">
-<D:set><D:prop><prop0 xmlns="http://webdav.org/neon/litmus/">value0</prop0></D:prop></D:set>
-<D:set><D:prop><prop1 xmlns="http://webdav.org/neon/litmus/">value1</prop1></D:prop></D:set>
-<D:set><D:prop><prop2 xmlns="http://webdav.org/neon/litmus/">value2</prop2></D:prop></D:set>
-<D:set><D:prop><prop3 xmlns="http://webdav.org/neon/litmus/">value3</prop3></D:prop></D:set>
-<D:set><D:prop><prop4 xmlns="http://webdav.org/neon/litmus/">value4</prop4></D:prop></D:set>
-<D:set><D:prop><prop5 xmlns="http://webdav.org/neon/litmus/">value5</prop5></D:prop></D:set>
-<D:set><D:prop><prop6 xmlns="http://webdav.org/neon/litmus/">value6</prop6></D:prop></D:set>
-<D:set><D:prop><prop7 xmlns="http://webdav.org/neon/litmus/">value7</prop7></D:prop></D:set>
-<D:set><D:prop><prop8 xmlns="http://webdav.org/neon/litmus/">value8</prop8></D:prop></D:set>
-<D:set><D:prop><prop9 xmlns="http://webdav.org/neon/litmus/">value9</prop9></D:prop></D:set>
-</D:propertyupdate>

Copied: CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPPATCH_request.xml (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/xml/PROPPATCH_request.xml)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPPATCH_request.xml	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/data/xml/PROPPATCH_request.xml	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,12 @@
+<D:propertyupdate xmlns:D="DAV:">
+<D:set><D:prop><prop0 xmlns="http://webdav.org/neon/litmus/">value0</prop0></D:prop></D:set>
+<D:set><D:prop><prop1 xmlns="http://webdav.org/neon/litmus/">value1</prop1></D:prop></D:set>
+<D:set><D:prop><prop2 xmlns="http://webdav.org/neon/litmus/">value2</prop2></D:prop></D:set>
+<D:set><D:prop><prop3 xmlns="http://webdav.org/neon/litmus/">value3</prop3></D:prop></D:set>
+<D:set><D:prop><prop4 xmlns="http://webdav.org/neon/litmus/">value4</prop4></D:prop></D:set>
+<D:set><D:prop><prop5 xmlns="http://webdav.org/neon/litmus/">value5</prop5></D:prop></D:set>
+<D:set><D:prop><prop6 xmlns="http://webdav.org/neon/litmus/">value6</prop6></D:prop></D:set>
+<D:set><D:prop><prop7 xmlns="http://webdav.org/neon/litmus/">value7</prop7></D:prop></D:set>
+<D:set><D:prop><prop8 xmlns="http://webdav.org/neon/litmus/">value8</prop8></D:prop></D:set>
+<D:set><D:prop><prop9 xmlns="http://webdav.org/neon/litmus/">value9</prop9></D:prop></D:set>
+</D:propertyupdate>

Deleted: CalendarServer/trunk/twext/web2/dav/test/data/xml/REPORT_request.xml
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/xml/REPORT_request.xml	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/test/data/xml/REPORT_request.xml	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<D:expand-property xmlns:D="DAV:">
-  <D:property name="version-history">
-    <D:property name="version-set">
-      <D:property name="creator-displayname"/>
-      <D:property name="activity-set"/>
-    </D:property>
-  </D:property>
-</D:expand-property>

Copied: CalendarServer/trunk/twext/web2/dav/test/data/xml/REPORT_request.xml (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/xml/REPORT_request.xml)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/data/xml/REPORT_request.xml	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/data/xml/REPORT_request.xml	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<D:expand-property xmlns:D="DAV:">
+  <D:property name="version-history">
+    <D:property name="version-set">
+      <D:property name="creator-displayname"/>
+      <D:property name="activity-set"/>
+    </D:property>
+  </D:property>
+</D:expand-property>

Deleted: CalendarServer/trunk/twext/web2/dav/test/data/xml/REPORT_response.xml
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/xml/REPORT_response.xml	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/test/data/xml/REPORT_response.xml	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<D:multistatus xmlns:D="DAV:">
-  <D:response>
-    <D:href>http://www.webdav.org/foo.html</D:href>
-    <D:propstat>
-      <D:prop>
-        <D:version-history>
-          <D:response>
-            <D:href>http://repo.webdav.org/his/23</D:href>
-            <D:propstat>
-              <D:prop>
-                <D:version-set>
-                  <D:response>
-                    <D:href>http://repo.webdav.org/his/23/ver/1</D:href>
-                    <D:propstat>
-                      <D:prop>
-                        <D:creator-displayname>Fred</D:creator-displayname>
-                        <D:activity-set>
-                          <D:href>
-                               http://www.webdav.org/ws/dev/sally
-                             </D:href>
-                        </D:activity-set>
-                      </D:prop>
-                      <D:status>HTTP/1.1 200 OK</D:status>
-                    </D:propstat>
-                  </D:response>
-                  <D:response>
-                    <D:href>http://repo.webdav.org/his/23/ver/2</D:href>
-                    <D:propstat>
-                      <D:prop>
-                        <D:creator-displayname>Sally</D:creator-displayname>
-                        <D:activity-set>
-                          <D:href>http://repo.webdav.org/act/add-refresh-cmd</D:href>
-                        </D:activity-set>
-                      </D:prop>
-                      <D:status>HTTP/1.1 200 OK</D:status>
-                    </D:propstat>
-                  </D:response>
-                </D:version-set>
-              </D:prop>
-              <D:status>HTTP/1.1 200 OK</D:status>
-            </D:propstat>
-          </D:response>
-        </D:version-history>
-      </D:prop>
-      <D:status>HTTP/1.1 200 OK</D:status>
-    </D:propstat>
-  </D:response>
-</D:multistatus>

Copied: CalendarServer/trunk/twext/web2/dav/test/data/xml/REPORT_response.xml (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/data/xml/REPORT_response.xml)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/data/xml/REPORT_response.xml	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/data/xml/REPORT_response.xml	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<D:multistatus xmlns:D="DAV:">
+  <D:response>
+    <D:href>http://www.webdav.org/foo.html</D:href>
+    <D:propstat>
+      <D:prop>
+        <D:version-history>
+          <D:response>
+            <D:href>http://repo.webdav.org/his/23</D:href>
+            <D:propstat>
+              <D:prop>
+                <D:version-set>
+                  <D:response>
+                    <D:href>http://repo.webdav.org/his/23/ver/1</D:href>
+                    <D:propstat>
+                      <D:prop>
+                        <D:creator-displayname>Fred</D:creator-displayname>
+                        <D:activity-set>
+                          <D:href>
+                               http://www.webdav.org/ws/dev/sally
+                             </D:href>
+                        </D:activity-set>
+                      </D:prop>
+                      <D:status>HTTP/1.1 200 OK</D:status>
+                    </D:propstat>
+                  </D:response>
+                  <D:response>
+                    <D:href>http://repo.webdav.org/his/23/ver/2</D:href>
+                    <D:propstat>
+                      <D:prop>
+                        <D:creator-displayname>Sally</D:creator-displayname>
+                        <D:activity-set>
+                          <D:href>http://repo.webdav.org/act/add-refresh-cmd</D:href>
+                        </D:activity-set>
+                      </D:prop>
+                      <D:status>HTTP/1.1 200 OK</D:status>
+                    </D:propstat>
+                  </D:response>
+                </D:version-set>
+              </D:prop>
+              <D:status>HTTP/1.1 200 OK</D:status>
+            </D:propstat>
+          </D:response>
+        </D:version-history>
+      </D:prop>
+      <D:status>HTTP/1.1 200 OK</D:status>
+    </D:propstat>
+  </D:response>
+</D:multistatus>

Copied: CalendarServer/trunk/twext/web2/dav/test/test_acl.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_acl.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_acl.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_acl.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,397 @@
+##
+# 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
+##
+
+import os
+
+from twisted.cred.portal import Portal
+
+from twext.web2 import responsecode
+from twext.web2.auth import basic
+from twext.web2.stream import MemoryStream
+from twext.web2.dav import davxml
+
+from twext.web2.dav.util import davXMLFromStream
+from twext.web2.dav.auth import TwistedPasswordProperty, IPrincipal, DavRealm, TwistedPropertyChecker, AuthenticationWrapper
+from twext.web2.dav.fileop import rmdir
+
+import twext.web2.dav.test.util
+from twext.web2.test.test_server import SimpleRequest
+from twext.web2.dav.test.util import Site, serialize
+from twext.web2.dav.test.test_resource import \
+    TestDAVPrincipalResource, TestPrincipalsCollection
+
+class ACL(twext.web2.dav.test.util.TestCase):
+    """
+    RFC 3744 (WebDAV ACL) tests.
+    """
+    def createDocumentRoot(self):
+        docroot = self.mktemp()
+        os.mkdir(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(
+            docroot, principalCollections=(principalCollection,))
+
+        portal = Portal(DavRealm())
+        portal.registerChecker(TwistedPropertyChecker())
+
+        credentialFactories = (basic.BasicCredentialFactory(""),)
+
+        loginInterfaces = (IPrincipal,)
+
+        self.site = Site(AuthenticationWrapper(
+            rootResource,
+            portal,
+            credentialFactories,
+            loginInterfaces
+        ))
+
+        rootResource.setAccessControlList(self.grant(davxml.All()))
+
+        for name, acl in (
+            ("none"       , self.grant()),
+            ("read"       , self.grant(davxml.Read())),
+            ("read-write" , self.grant(davxml.Read(), davxml.Write())),
+            ("unlock"     , self.grant(davxml.Unlock())),
+            ("all"        , self.grant(davxml.All())),
+        ):
+            filename = os.path.join(docroot, name)
+            if not os.path.isfile(filename):
+                file(filename, "w").close()
+            resource = self.resource_class(filename)
+            resource.setAccessControlList(acl)
+
+        for name, acl in (
+            ("nobind" , self.grant()),
+            ("bind"   , self.grant(davxml.Bind())),
+            ("unbind" , self.grant(davxml.Bind(), davxml.Unbind())),
+        ):
+            dirname = os.path.join(docroot, name)
+            if not os.path.isdir(dirname):
+                os.mkdir(dirname)
+            resource = self.resource_class(dirname)
+            resource.setAccessControlList(acl)
+        return docroot
+
+
+    def restore(self):
+        # Get rid of whatever messed up state the test has now so that we'll
+        # get a fresh docroot.  This isn't very cool; tests should be doing
+        # less so that they don't need a fresh copy of this state.
+        if hasattr(self, "_docroot"):
+            rmdir(self._docroot)
+            del self._docroot
+
+    def test_COPY_MOVE_source(self):
+        """
+        Verify source access controls during COPY and MOVE.
+        """
+        def work():
+            dst_path = os.path.join(self.docroot, "copy_dst")
+            dst_uri = "/" + os.path.basename(dst_path)
+
+            for src, status in (
+                ("nobind", responsecode.FORBIDDEN),
+                ("bind",   responsecode.FORBIDDEN),
+                ("unbind", responsecode.CREATED),
+            ):
+                src_path = os.path.join(self.docroot, "src_" + src)
+                src_uri = "/" + os.path.basename(src_path)
+                if not os.path.isdir(src_path):
+                    os.mkdir(src_path)
+                src_resource = self.resource_class(src_path)
+                src_resource.setAccessControlList({
+                    "nobind": self.grant(),
+                    "bind"  : self.grant(davxml.Bind()),
+                    "unbind": self.grant(davxml.Bind(), davxml.Unbind())
+                }[src])
+                for name, acl in (
+                    ("none"       , self.grant()),
+                    ("read"       , self.grant(davxml.Read())),
+                    ("read-write" , self.grant(davxml.Read(), davxml.Write())),
+                    ("unlock"     , self.grant(davxml.Unlock())),
+                    ("all"        , self.grant(davxml.All())),
+                ):
+                    filename = os.path.join(src_path, name)
+                    if not os.path.isfile(filename):
+                        file(filename, "w").close()
+                    self.resource_class(filename).setAccessControlList(acl)
+
+                for method in ("COPY", "MOVE"):
+                    for name, code in (
+                        ("none"       , {"COPY": responsecode.FORBIDDEN, "MOVE": status}[method]),
+                        ("read"       , {"COPY": responsecode.CREATED,   "MOVE": status}[method]),
+                        ("read-write" , {"COPY": responsecode.CREATED,   "MOVE": status}[method]),
+                        ("unlock"     , {"COPY": responsecode.FORBIDDEN, "MOVE": status}[method]),
+                        ("all"        , {"COPY": responsecode.CREATED,   "MOVE": status}[method]),
+                    ):
+                        path = os.path.join(src_path, name)
+                        uri = src_uri + "/" + name
+    
+                        request = SimpleRequest(self.site, method, uri)
+                        request.headers.setHeader("destination", dst_uri)
+                        _add_auth_header(request)
+    
+                        def test(response, code=code, path=path):
+                            if os.path.isfile(dst_path):
+                                os.remove(dst_path)
+    
+                            if response.code != code:
+                                return self.oops(request, response, code, method, name)
+    
+                        yield (request, test)
+
+        return serialize(self.send, work())
+
+    def test_COPY_MOVE_dest(self):
+        """
+        Verify destination access controls during COPY and MOVE.
+        """
+        def work():
+            src_path = os.path.join(self.docroot, "read")
+            uri = "/" + os.path.basename(src_path)
+
+            for method in ("COPY", "MOVE"):
+                for name, code in (
+                    ("nobind" , responsecode.FORBIDDEN),
+                    ("bind"   , responsecode.CREATED),
+                    ("unbind" , responsecode.CREATED),
+                ):
+                    dst_parent_path = os.path.join(self.docroot, name)
+                    dst_path = os.path.join(dst_parent_path, "dst")
+
+                    request = SimpleRequest(self.site, method, uri)
+                    request.headers.setHeader("destination", "/" + name + "/dst")
+                    _add_auth_header(request)
+
+                    def test(response, code=code, dst_path=dst_path):
+                        if os.path.isfile(dst_path):
+                            os.remove(dst_path)
+
+                        if response.code != code:
+                            return self.oops(request, response, code, method, name)
+
+                    yield (request, test)
+                    self.restore()
+
+        return serialize(self.send, work())
+
+    def test_DELETE(self):
+        """
+        Verify access controls during DELETE.
+        """
+        def work():
+            for name, code in (
+                ("nobind" , responsecode.FORBIDDEN),
+                ("bind"   , responsecode.FORBIDDEN),
+                ("unbind" , responsecode.NO_CONTENT),
+            ):
+                collection_path = os.path.join(self.docroot, name)
+                path = os.path.join(collection_path, "dst")
+
+                file(path, "w").close()
+
+                request = SimpleRequest(self.site, "DELETE", "/" + name + "/dst")
+                _add_auth_header(request)
+
+                def test(response, code=code, path=path):
+                    if response.code != code:
+                        return self.oops(request, response, code, "DELETE", name)
+
+                yield (request, test)
+
+        return serialize(self.send, work())
+
+    def test_UNLOCK(self):
+        """
+        Verify access controls during UNLOCK of unowned lock.
+        """
+        raise NotImplementedError()
+
+    test_UNLOCK.todo = "access controls on UNLOCK unimplemented"
+
+    def test_MKCOL_PUT(self):
+        """
+        Verify access controls during MKCOL.
+        """
+        for method in ("MKCOL", "PUT"):
+            def work():
+                for name, code in (
+                    ("nobind" , responsecode.FORBIDDEN),
+                    ("bind"   , responsecode.CREATED),
+                    ("unbind" , responsecode.CREATED),
+                ):
+                    collection_path = os.path.join(self.docroot, name)
+                    path = os.path.join(collection_path, "dst")
+
+                    if os.path.isfile(path):
+                        os.remove(path)
+                    elif os.path.isdir(path):
+                        os.rmdir(path)
+
+                    request = SimpleRequest(self.site, method, "/" + name + "/dst")
+                    _add_auth_header(request)
+
+                    def test(response, code=code, path=path):
+                        if response.code != code:
+                            return self.oops(request, response, code, method, name)
+
+                    yield (request, test)
+
+        return serialize(self.send, work())
+
+    def test_PUT_exists(self):
+        """
+        Verify access controls during PUT of existing file.
+        """
+        def work():
+            for name, code in (
+                ("none"       , responsecode.FORBIDDEN),
+                ("read"       , responsecode.FORBIDDEN),
+                ("read-write" , responsecode.NO_CONTENT),
+                ("unlock"     , responsecode.FORBIDDEN),
+                ("all"        , responsecode.NO_CONTENT),
+            ):
+                path = os.path.join(self.docroot, name)
+
+                request = SimpleRequest(self.site, "PUT", "/" + name)
+                _add_auth_header(request)
+
+                def test(response, code=code, path=path):
+                    if response.code != code:
+                        return self.oops(request, response, code, "PUT", name)
+
+                yield (request, test)
+
+        return serialize(self.send, work())
+
+    def test_PROPFIND(self):
+        """
+        Verify access controls during PROPFIND.
+        """
+        raise NotImplementedError()
+
+    test_PROPFIND.todo = "access controls on PROPFIND unimplemented"
+
+    def test_PROPPATCH(self):
+        """
+        Verify access controls during PROPPATCH.
+        """
+        def work():
+            for name, code in (
+                ("none"       , responsecode.FORBIDDEN),
+                ("read"       , responsecode.FORBIDDEN),
+                ("read-write" , responsecode.MULTI_STATUS),
+                ("unlock"     , responsecode.FORBIDDEN),
+                ("all"        , responsecode.MULTI_STATUS),
+            ):
+                path = os.path.join(self.docroot, name)
+
+                request = SimpleRequest(self.site, "PROPPATCH", "/" + name)
+                request.stream = MemoryStream(
+                    davxml.WebDAVDocument(davxml.PropertyUpdate()).toxml()
+                )
+                _add_auth_header(request)
+
+                def test(response, code=code, path=path):
+                    if response.code != code:
+                        return self.oops(request, response, code, "PROPPATCH", name)
+
+                yield (request, test)
+
+        return serialize(self.send, work())
+
+    def test_GET_REPORT(self):
+        """
+        Verify access controls during GET and REPORT.
+        """
+        def work():
+            for method in ("GET", "REPORT"):
+                if method == "GET":
+                    ok = responsecode.OK
+                elif method == "REPORT":
+                    ok = responsecode.MULTI_STATUS
+                else:
+                    raise AssertionError("We shouldn't be here.  (method = %r)" % (method,))
+
+                for name, code in (
+                    ("none"       , responsecode.FORBIDDEN),
+                    ("read"       , ok),
+                    ("read-write" , ok),
+                    ("unlock"     , responsecode.FORBIDDEN),
+                    ("all"        , ok),
+                ):
+                    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):
+                        if response.code != code:
+                            return self.oops(request, response, code, method, name)
+
+                    yield (request, test)
+
+        return serialize(self.send, work())
+
+    def oops(self, request, response, code, method, name):
+        def gotResponseData(doc):
+            if doc is None:
+                doc_xml = None
+            else:
+                doc_xml = doc.toxml()
+    
+            def fail(acl):
+                self.fail("Incorrect status code %s (!= %s) for %s of resource %s with %s ACL: %s\nACL: %s"
+                          % (response.code, code, method, request.uri, name, doc_xml, acl.toxml()))
+
+
+            def getACL(resource):
+                return resource.accessControlList(request)
+
+            d = request.locateResource(request.uri)
+            d.addCallback(getACL)
+            d.addCallback(fail)
+            return d
+
+        d = davXMLFromStream(response.stream)
+        d.addCallback(gotResponseData)
+        return d
+
+def _add_auth_header(request):
+    request.headers.setHeader(
+        "authorization",
+        ("basic", "user01:user01".encode("base64"))
+    )

Copied: CalendarServer/trunk/twext/web2/dav/test/test_copy.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_copy.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_copy.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_copy.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,188 @@
+##
+# 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 hashlib import md5
+import os
+import urllib
+
+import twext.web2.dav.test.util
+from twext.web2 import responsecode
+from twext.web2.test.test_server import SimpleRequest
+from twext.web2.dav.test.util import dircmp, serialize
+from twext.web2.dav.fileop import rmdir
+
+class COPY(twext.web2.dav.test.util.TestCase):
+    """
+    COPY request
+    """
+    # FIXME:
+    # Check that properties are being copied
+    def test_COPY_create(self):
+        """
+        COPY to new resource.
+        """
+        def test(response, path, isfile, sum, uri, depth, dst_path):
+            if response.code != responsecode.CREATED:
+                self.fail("Incorrect response code for COPY %s (depth=%r): %s != %s"
+                          % (uri, depth, response.code, responsecode.CREATED))
+
+            if response.headers.getHeader("location") is None:
+                self.fail("Reponse to COPY %s (depth=%r) with CREATE status is missing location: header."
+                          % (uri, depth))
+
+            if os.path.isfile(path):
+                if not os.path.isfile(dst_path):
+                    self.fail("COPY %s (depth=%r) produced no output file" % (uri, depth))
+                if not cmp(path, dst_path):
+                    self.fail("COPY %s (depth=%r) produced different file" % (uri, depth))
+                os.remove(dst_path)
+
+            elif os.path.isdir(path):
+                if not os.path.isdir(dst_path):
+                    self.fail("COPY %s (depth=%r) produced no output directory" % (uri, depth))
+
+                if depth in ("infinity", None):
+                    if dircmp(path, dst_path):
+                        self.fail("COPY %s (depth=%r) produced different directory" % (uri, depth))
+
+                elif depth == "0":
+                    for filename in os.listdir(dst_path):
+                        self.fail("COPY %s (depth=%r) shouldn't copy directory contents (eg. %s)" % (uri, depth, filename))
+
+                else: raise AssertionError("Unknown depth: %r" % (depth,))
+
+                rmdir(dst_path)
+
+            else:
+                self.fail("Source %s is neither a file nor a directory"
+                          % (path,))
+
+        return serialize(self.send, work(self, test))
+
+    def test_COPY_exists(self):
+        """
+        COPY to existing resource.
+        """
+        def test(response, path, isfile, sum, uri, depth, dst_path):
+            if response.code != responsecode.PRECONDITION_FAILED:
+                self.fail("Incorrect response code for COPY without overwrite %s: %s != %s"
+                          % (uri, response.code, responsecode.PRECONDITION_FAILED))
+            else:
+                # FIXME: Check XML error code (2518bis)
+                pass
+
+        return serialize(self.send, work(self, test, overwrite=False))
+
+    def test_COPY_overwrite(self):
+        """
+        COPY to existing resource with overwrite header.
+        """
+        def test(response, path, isfile, sum, uri, depth, dst_path):
+            if response.code != responsecode.NO_CONTENT:
+                self.fail("Incorrect response code for COPY with overwrite %s: %s != %s"
+                          % (uri, response.code, responsecode.NO_CONTENT))
+            else:
+                # FIXME: Check XML error code (2518bis)
+                pass
+
+            self.failUnless(os.path.exists(dst_path), "COPY didn't produce file: %s" % (dst_path,))
+
+        return serialize(self.send, work(self, test, overwrite=True))
+
+    def test_COPY_no_parent(self):
+        """
+        COPY to resource with no parent.
+        """
+        def test(response, path, isfile, sum, uri, depth, dst_path):
+            if response.code != responsecode.CONFLICT:
+                self.fail("Incorrect response code for COPY with no parent %s: %s != %s"
+                          % (uri, response.code, responsecode.CONFLICT))
+            else:
+                # FIXME: Check XML error code (2518bis)
+                pass
+
+        return serialize(self.send, work(self, test, dst=os.path.join(self.docroot, "elvislives!")))
+
+def work(self, test, overwrite=None, dst=None, depths=("0", "infinity", None)):
+    if dst is None:
+        dst = os.path.join(self.docroot, "dst")
+        os.mkdir(dst)
+
+    for basename in os.listdir(self.docroot):
+        if basename == "dst": continue
+
+        uri = urllib.quote("/" + basename)
+        path = os.path.join(self.docroot, basename)
+        isfile = os.path.isfile(path)
+        sum = sumFile(path)
+        dst_path = os.path.join(dst, basename)
+        dst_uri = urllib.quote("/dst/" + basename)
+
+        if not isfile:
+            uri += "/"
+            dst_uri += "/"
+
+        if overwrite is not None:
+            # Create a file at dst_path to create a conflict
+            file(dst_path, "w").close()
+
+        for depth in depths:
+            def do_test(response, path=path, isfile=isfile, sum=sum, uri=uri, depth=depth, dst_path=dst_path):
+                test(response, path, isfile, sum, uri, depth, dst_path)
+
+            request = SimpleRequest(self.site, self.__class__.__name__, uri)
+            request.headers.setHeader("destination", dst_uri)
+            if depth is not None:
+                request.headers.setHeader("depth", depth)
+            if overwrite is not None:
+                request.headers.setHeader("overwrite", overwrite)
+
+            yield (request, do_test)
+
+def sumFile(path):
+    m = md5()
+
+    if os.path.isfile(path):
+        f = file(path)
+        try:
+            m.update(f.read())
+        finally:
+            f.close()
+
+    elif os.path.isdir(path):
+        for dir, subdirs, files in os.walk(path):
+            for filename in files:
+                m.update(filename)
+                f = file(os.path.join(dir, filename))
+                try:
+                    m.update(f.read())
+                finally:
+                    f.close()
+            for dirname in subdirs:
+                m.update(dirname + "/")
+
+    else:
+        raise AssertionError()
+
+    return m.digest()

Modified: CalendarServer/trunk/twext/web2/dav/test/test_davxml.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_davxml.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/dav/test/test_davxml.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,17 +1,26 @@
+
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Computer, 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
+# 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.
 #
-# 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: Wilfredo Sanchez, wsanchez at apple.com
 ##
 
 from twext.web2.dav.davxml import *

Copied: CalendarServer/trunk/twext/web2/dav/test/test_delete.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_delete.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_delete.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_delete.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,74 @@
+##
+# 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
+##
+
+import os
+import urllib
+import random
+
+from twext.web2 import responsecode
+from twext.web2.iweb import IResponse
+from twext.web2.test.test_server import SimpleRequest
+from twext.web2.dav.test.util import serialize
+import twext.web2.dav.test.util
+
+class DELETE(twext.web2.dav.test.util.TestCase):
+    """
+    DELETE request
+    """
+    # FIXME:
+    # Try setting unwriteable perms on file, then delete
+    # Try check response XML for error in some but not all files
+
+    def test_DELETE(self):
+        """
+        DELETE request
+        """
+        def check_result(response, path):
+            response = IResponse(response)
+
+            if response.code != responsecode.NO_CONTENT:
+                self.fail("DELETE response %s != %s" % (response.code, responsecode.NO_CONTENT))
+
+            if os.path.exists(path):
+                self.fail("DELETE did not remove path %s" % (path,))
+
+        def work():
+            for filename in os.listdir(self.docroot):
+                path = os.path.join(self.docroot, filename)
+                uri = urllib.quote("/" + filename)
+
+                if os.path.isdir(path): uri = uri + "/"
+
+                def do_test(response, path=path):
+                    return check_result(response, path)
+
+                request = SimpleRequest(self.site, "DELETE", uri)
+
+                depth = random.choice(("infinity", None))
+                if depth is not None:
+                    request.headers.setHeader("depth", depth)
+
+                yield (request, do_test)
+
+        return serialize(self.send, work())

Copied: CalendarServer/trunk/twext/web2/dav/test/test_http.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_http.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_http.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_http.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,89 @@
+##
+# 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
+##
+
+import errno
+
+from twisted.python.failure import Failure
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError
+from twext.web2.dav.http import ErrorResponse, statusForFailure
+import twext.web2.dav.test.util
+
+class HTTP(twext.web2.dav.test.util.TestCase):
+    """
+    HTTP Utilities
+    """
+    def test_statusForFailure_errno(self):
+        """
+        statusForFailure() for exceptions with known errno values
+        """
+        for ex_class in (IOError, OSError):
+            for exception, result in (
+                (ex_class(errno.EACCES, "Permission denied" ), responsecode.FORBIDDEN),
+                (ex_class(errno.EPERM , "Permission denied" ), responsecode.FORBIDDEN),
+                (ex_class(errno.ENOSPC, "No space available"), responsecode.INSUFFICIENT_STORAGE_SPACE),
+                (ex_class(errno.ENOENT, "No such file"      ), responsecode.NOT_FOUND),
+            ):
+                self._check_exception(exception, result)
+
+    def test_statusForFailure_HTTPError(self):
+        """
+        statusForFailure() for HTTPErrors
+        """
+        for code in responsecode.RESPONSES:
+            self._check_exception(HTTPError(code), code)
+            self._check_exception(HTTPError(ErrorResponse(code, ("http://twistedmatrix.com/", "bar"))), code)
+
+    def test_statusForFailure_exception(self):
+        """
+        statusForFailure() for known/unknown exceptions
+        """
+        for exception, result in (
+            (NotImplementedError("Duh..."), responsecode.NOT_IMPLEMENTED),
+        ):
+            self._check_exception(exception, result)
+
+        class UnknownException (Exception):
+            pass
+
+        try:
+            self._check_exception(UnknownException(), None)
+        except UnknownException:
+            pass
+        else:
+            self.fail("Unknown exception should have re-raised.")
+
+    def _check_exception(self, exception, result):
+        try:
+            raise exception
+        except Exception, e:
+            failure = Failure()
+            status = statusForFailure(failure)
+            self.failUnless(
+                status == result,
+                "Failure %r (%s) generated incorrect status code: %s != %s"
+                % (failure, failure.value, status, result)
+            )
+        else:
+            raise AssertionError("We shouldn't be here.")

Copied: CalendarServer/trunk/twext/web2/dav/test/test_lock.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_lock.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_lock.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_lock.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,53 @@
+##
+# 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.trial.unittest import SkipTest
+import twext.web2.dav.test.util
+
+class LOCK_UNLOCK(twext.web2.dav.test.util.TestCase):
+    """
+    LOCK, UNLOCK requests
+    """
+    # FIXME:
+    # Check PUT
+    # Check POST
+    # Check PROPPATCH
+    # Check LOCK
+    # Check UNLOCK
+    # Check MOVE, COPY
+    # Check DELETE
+    # Check MKCOL
+    # Check null resource
+    # Check collections
+    # Check depth
+    # Check If header
+    # Refresh lock
+
+    def test_LOCK_UNLOCK(self):
+        """
+        LOCK, UNLOCK request
+        """
+        raise SkipTest("test unimplemented")
+
+    test_LOCK_UNLOCK.todo = "LOCK/UNLOCK unimplemented"

Copied: CalendarServer/trunk/twext/web2/dav/test/test_mkcol.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_mkcol.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_mkcol.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_mkcol.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,85 @@
+##
+# 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
+##
+
+import os
+
+from twext.web2 import responsecode
+from twext.web2.iweb import IResponse
+from twext.web2.stream import MemoryStream
+from twext.web2.dav.fileop import rmdir
+from twext.web2.test.test_server import SimpleRequest
+import twext.web2.dav.test.util
+
+class MKCOL(twext.web2.dav.test.util.TestCase):
+    """
+    MKCOL request
+    """
+    # FIXME:
+    # Try in nonexistant parent collection.
+    # Try on existing resource.
+    # Try with request body?
+    def test_MKCOL(self):
+        """
+        MKCOL request
+        """
+        path, uri = self.mkdtemp("collection")
+
+        rmdir(path)
+
+        def check_result(response):
+            response = IResponse(response)
+
+            if response.code != responsecode.CREATED:
+                self.fail("MKCOL response %s != %s" % (response.code, responsecode.CREATED))
+
+            if not os.path.isdir(path):
+                self.fail("MKCOL did not create directory %s" % (path,))
+
+        request = SimpleRequest(self.site, "MKCOL", uri)
+
+        return self.send(request, check_result)
+
+    def test_MKCOL_invalid_body(self):
+        """
+        MKCOL request with invalid request body
+        (Any body at all is invalid in our implementation; there is no
+        such thing as a valid body.)
+        """
+        path, uri = self.mkdtemp("collection")
+
+        rmdir(path)
+
+        def check_result(response):
+            response = IResponse(response)
+
+            if response.code != responsecode.UNSUPPORTED_MEDIA_TYPE:
+                self.fail("MKCOL response %s != %s" % (response.code, responsecode.UNSUPPORTED_MEDIA_TYPE))
+
+            if os.path.isdir(path):
+                self.fail("MKCOL incorrectly created directory %s" % (path,))
+
+        request = SimpleRequest(self.site, "MKCOL", uri)
+        request.stream = MemoryStream("This is not a valid MKCOL request body.")
+
+        return self.send(request, check_result)

Copied: CalendarServer/trunk/twext/web2/dav/test/test_move.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_move.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_move.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_move.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,108 @@
+##
+# 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
+##
+
+import os
+
+import twext.web2.dav.test.util
+import twext.web2.dav.test.test_copy
+from twext.web2 import responsecode
+from twext.web2.dav.test.util import serialize
+from twext.web2.dav.test.test_copy import sumFile
+
+class MOVE(twext.web2.dav.test.util.TestCase):
+    """
+    MOVE request
+    """
+    # FIXME:
+    # Check that properties are being moved
+    def test_MOVE_create(self):
+        """
+        MOVE to new resource.
+        """
+        def test(response, path, isfile, sum, uri, depth, dst_path):
+            if response.code != responsecode.CREATED:
+                self.fail("Incorrect response code for MOVE %s: %s != %s"
+                          % (uri, response.code, responsecode.CREATED))
+
+            if response.headers.getHeader("location") is None:
+                self.fail("Reponse to MOVE %s with CREATE status is missing location: header."
+                          % (uri,))
+
+            if isfile:
+                if not os.path.isfile(dst_path):
+                    self.fail("MOVE %s produced no output file" % (uri,))
+                if sum != sumFile(dst_path):
+                    self.fail("MOVE %s produced different file" % (uri,))
+            else:
+                if not os.path.isdir(dst_path):
+                    self.fail("MOVE %s produced no output directory" % (uri,))
+                if sum != sumFile(dst_path):
+                    self.fail("isdir %s produced different directory" % (uri,))
+
+        return serialize(self.send, work(self, test))
+
+    def test_MOVE_exists(self):
+        """
+        MOVE to existing resource.
+        """
+        def test(response, path, isfile, sum, uri, depth, dst_path):
+            if response.code != responsecode.PRECONDITION_FAILED:
+                self.fail("Incorrect response code for MOVE without overwrite %s: %s != %s"
+                          % (uri, response.code, responsecode.PRECONDITION_FAILED))
+            else:
+                # FIXME: Check XML error code (2518bis)
+                pass
+
+        return serialize(self.send, work(self, test, overwrite=False))
+
+    def test_MOVE_overwrite(self):
+        """
+        MOVE to existing resource with overwrite header.
+        """
+        def test(response, path, isfile, sum, uri, depth, dst_path):
+            if response.code != responsecode.NO_CONTENT:
+                self.fail("Incorrect response code for MOVE with overwrite %s: %s != %s"
+                          % (uri, response.code, responsecode.NO_CONTENT))
+            else:
+                # FIXME: Check XML error code (2518bis)
+                pass
+
+        return serialize(self.send, work(self, test, overwrite=True))
+
+    def test_MOVE_no_parent(self):
+        """
+        MOVE to resource with no parent.
+        """
+        def test(response, path, isfile, sum, uri, depth, dst_path):
+            if response.code != responsecode.CONFLICT:
+                self.fail("Incorrect response code for MOVE with no parent %s: %s != %s"
+                          % (uri, response.code, responsecode.CONFLICT))
+            else:
+                # FIXME: Check XML error code (2518bis)
+                pass
+
+        return serialize(self.send, work(self, test, dst=os.path.join(self.docroot, "elvislives!")))
+
+def work(self, test, overwrite=None, dst=None):
+    return twext.web2.dav.test.test_copy.work(self, test, overwrite, dst, depths=(None,))

Copied: CalendarServer/trunk/twext/web2/dav/test/test_options.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_options.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_options.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_options.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,64 @@
+##
+# 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 twext.web2.iweb import IResponse
+
+import twext.web2.dav.test.util
+from twext.web2.test.test_server import SimpleRequest
+
+class OPTIONS(twext.web2.dav.test.util.TestCase):
+    """
+    OPTIONS request
+    """
+    def test_DAV1(self):
+        """
+        DAV level 1
+        """
+        return self._test_level("1")
+
+    def test_DAV2(self):
+        """
+        DAV level 2
+        """
+        return self._test_level("2")
+
+    test_DAV2.todo = "DAV level 2 unimplemented"
+
+    def test_ACL(self):
+        """
+        DAV ACL
+        """
+        return self._test_level("access-control")
+
+    def _test_level(self, level):
+        def doTest(response):
+            response = IResponse(response)
+
+            dav = response.headers.getHeader("dav")
+            if not dav: self.fail("no DAV header: %s" % (response.headers,))
+            self.assertIn(level, dav, "no DAV level %s header" % (level,))
+
+            return response
+
+        return self.send(SimpleRequest(self.site, "OPTIONS", "/"), doTest)

Copied: CalendarServer/trunk/twext/web2/dav/test/test_pipeline.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_pipeline.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_pipeline.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_pipeline.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -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 twext.web2.test import test_server
+from twext.web2 import resource
+from twext.web2 import http
+from twext.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/twext/web2/dav/test/test_prop.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_prop.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_prop.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_prop.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,335 @@
+##
+# 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 twext.web2.dav.element.rfc4331 import QuotaUsedBytes
+from twext.web2.dav.element.rfc4331 import QuotaAvailableBytes
+
+import random
+
+from twisted.trial.unittest import SkipTest
+from twext.web2 import responsecode
+from twext.web2.iweb import IResponse
+from twext.web2.stream import MemoryStream
+from twext.web2 import http_headers
+from twext.web2.dav import davxml
+from twext.web2.dav.resource import DAVResource
+from twext.web2.dav.davxml import dav_namespace, lookupElement
+from twext.web2.dav.util import davXMLFromStream
+from twext.web2.test.test_server import SimpleRequest
+from twext.web2.dav.test.util import serialize
+import twext.web2.dav.test.util
+
+# Remove dynamic live properties that exist
+dynamicLiveProperties = (
+    (dav_namespace, "quota-available-bytes"     ),
+    (dav_namespace, "quota-used-bytes"          ),
+)
+
+
+#
+# See whether dead properties are available
+#
+from twext.web2.dav.noneprops import NonePropertyStore
+from twext.web2.dav.static import DeadPropertyStore
+
+class PROP(twext.web2.dav.test.util.TestCase):
+    """
+    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
+        """
+        def check_result(response):
+            response = IResponse(response)
+
+            if response.code != responsecode.MULTI_STATUS:
+                self.fail("Incorrect response code for PROPFIND (%s != %s)"
+                          % (response.code, responsecode.MULTI_STATUS))
+
+            content_type = response.headers.getHeader("content-type")
+            if content_type not in (http_headers.MimeType("text", "xml"),
+                                    http_headers.MimeType("application", "xml")):
+                self.fail("Incorrect content-type for PROPFIND response (%r not in %r)"
+                          % (content_type, (http_headers.MimeType("text", "xml"),
+                                            http_headers.MimeType("application", "xml"))))
+
+            return davXMLFromStream(response.stream).addCallback(check_xml)
+
+        def check_xml(doc):
+            multistatus = doc.root_element
+
+            if not isinstance(multistatus, davxml.MultiStatus):
+                self.fail("PROPFIND response XML root element is not multistatus: %r" % (multistatus,))
+
+            for response in multistatus.childrenOfType(davxml.PropertyStatusResponse):
+                if response.childOfType(davxml.HRef) == "/":
+                    for propstat in response.childrenOfType(davxml.PropertyStatus):
+                        status = propstat.childOfType(davxml.Status)
+                        properties = propstat.childOfType(davxml.PropertyContainer).children
+
+                        if status.code != responsecode.OK:
+                            self.fail("PROPFIND failed (status %s) to locate live properties: %s"
+                                      % (status.code, properties))
+
+                        properties_to_find = [p.qname() for p in self.liveProperties()]
+
+                        for property in properties:
+                            qname = property.qname()
+                            if qname in properties_to_find:
+                                properties_to_find.remove(qname)
+                            else:
+                                self.fail("PROPFIND found property we didn't ask for: %r" % (property,))
+
+                        if properties_to_find:
+                            self.fail("PROPFIND failed to find properties: %r" % (properties_to_find,))
+
+                    break
+
+            else:
+                self.fail("No response for URI /")
+
+        query = davxml.PropertyFind(davxml.PropertyContainer(*self.liveProperties()))
+
+        request = SimpleRequest(self.site, "PROPFIND", "/")
+
+        depth = random.choice(("0", "1", "infinity", None))
+        if depth is not None:
+            request.headers.setHeader("depth", depth)
+
+        request.stream = MemoryStream(query.toxml())
+
+        return self.send(request, check_result)
+
+    def test_PROPFIND_list(self):
+        """
+        PROPFIND with allprop, propname
+        """
+        def check_result(which):
+            def _check_result(response):
+                response = IResponse(response)
+
+                if response.code != responsecode.MULTI_STATUS:
+                    self.fail("Incorrect response code for PROPFIND (%s != %s)"
+                              % (response.code, responsecode.MULTI_STATUS))
+
+                return davXMLFromStream(response.stream).addCallback(check_xml, which)
+            return _check_result
+
+        def check_xml(doc, which):
+            response = doc.root_element.childOfType(davxml.PropertyStatusResponse)
+
+            self.failUnless(
+                response.childOfType(davxml.HRef) == "/",
+                "Incorrect response URI: %s != /" % (response.childOfType(davxml.HRef),)
+            )
+
+            for propstat in response.childrenOfType(davxml.PropertyStatus):
+                status = propstat.childOfType(davxml.Status)
+                properties = propstat.childOfType(davxml.PropertyContainer).children
+
+                if status.code != responsecode.OK:
+                    self.fail("PROPFIND failed (status %s) to locate live properties: %s"
+                              % (status.code, properties))
+
+                if which.name == "allprop":
+                    properties_to_find = [p.qname() for p in self.liveProperties() if not p.hidden]
+                else:
+                    properties_to_find = [p.qname() for p in self.liveProperties()]
+
+                for property in properties:
+                    qname = property.qname()
+                    if qname in properties_to_find:
+                        properties_to_find.remove(qname)
+                    elif qname[0] != dav_namespace:
+                        pass
+                    else:
+                        self.fail("PROPFIND with %s found property we didn't expect: %r" % (which.name, property))
+
+                    if which.name == "propname":
+                        # Element should be empty
+                        self.failUnless(len(property.children) == 0)
+                    else:
+                        # Element should have a value, unless the property exists and is empty...
+                        # Verify that there is a value for live properties for which we know
+                        # that this should be the case.
+                        if property.namespace == dav_namespace and property.name in (
+                            "getetag",
+                            "getcontenttype",
+                            "getlastmodified",
+                            "creationdate",
+                            "displayname",
+                        ):
+                            self.failIf(
+                                len(property.children) == 0,
+                                "Property has no children: %r" % (property.toxml(),)
+                            )
+
+                if properties_to_find:
+                    self.fail("PROPFIND with %s failed to find properties: %r" % (which.name, properties_to_find))
+
+            properties = propstat.childOfType(davxml.PropertyContainer).children
+
+        def work():
+            for which in (davxml.AllProperties(), davxml.PropertyName()):
+                query = davxml.PropertyFind(which)
+
+                request = SimpleRequest(self.site, "PROPFIND", "/")
+                request.headers.setHeader("depth", "0")
+                request.stream = MemoryStream(query.toxml())
+
+                yield (request, check_result(which))
+
+        return serialize(self.send, work())
+
+    def test_PROPPATCH_basic(self):
+        """
+        PROPPATCH
+        """
+        # FIXME:
+        # Do PROPFIND to make sure it's still there
+        # Test nonexistant resource
+        # Test None namespace in property
+
+        def check_patch_response(response):
+            response = IResponse(response)
+
+            if response.code != responsecode.MULTI_STATUS:
+                self.fail("Incorrect response code for PROPFIND (%s != %s)"
+                          % (response.code, responsecode.MULTI_STATUS))
+
+            content_type = response.headers.getHeader("content-type")
+            if content_type not in (http_headers.MimeType("text", "xml"),
+                                    http_headers.MimeType("application", "xml")):
+                self.fail("Incorrect content-type for PROPPATCH response (%r not in %r)"
+                          % (content_type, (http_headers.MimeType("text", "xml"),
+                                            http_headers.MimeType("application", "xml"))))
+
+            return davXMLFromStream(response.stream).addCallback(check_patch_xml)
+
+        def check_patch_xml(doc):
+            multistatus = doc.root_element
+
+            if not isinstance(multistatus, davxml.MultiStatus):
+                self.fail("PROPFIND response XML root element is not multistatus: %r" % (multistatus,))
+
+            # Requested a property change one resource, so there should be exactly one response
+            response = multistatus.childOfType(davxml.Response)
+
+            # Should have a response description (its contents are arbitrary)
+            response.childOfType(davxml.ResponseDescription)
+
+            # Requested property change was on /
+            self.failUnless(
+                response.childOfType(davxml.HRef) == "/",
+                "Incorrect response URI: %s != /" % (response.childOfType(davxml.HRef),)
+            )
+
+            # Requested one property change, so there should be exactly one property status
+            propstat = response.childOfType(davxml.PropertyStatus)
+
+            # And the contained property should be a SpiffyProperty
+            self.failIf(
+                propstat.childOfType(davxml.PropertyContainer).childOfType(SpiffyProperty) is None,
+                "Not a SpiffyProperty in PROPPATCH property status: %s" % (propstat.toxml())
+            )
+
+            # And the status should be 200
+            self.failUnless(
+                propstat.childOfType(davxml.Status).code == responsecode.OK,
+                "Incorrect status code for PROPPATCH of property %s: %s != %s"
+                % (propstat.childOfType(davxml.PropertyContainer).toxml(),
+                   propstat.childOfType(davxml.Status).code, responsecode.OK)
+            )
+
+        patch = davxml.PropertyUpdate(
+            davxml.Set(
+                davxml.PropertyContainer(
+                    SpiffyProperty.fromString("This is a spiffy resource.")
+                )
+            )
+        )
+
+        request = SimpleRequest(self.site, "PROPPATCH", "/")
+        request.stream = MemoryStream(patch.toxml())
+        return self.send(request, check_patch_response)
+
+    def test_PROPPATCH_liveprop(self):
+        """
+        PROPPATCH on a live property
+        """
+        prop = davxml.GETETag.fromString("some-etag-string")
+        patch = davxml.PropertyUpdate(davxml.Set(davxml.PropertyContainer(prop)))
+
+        return self._simple_PROPPATCH(patch, prop, responsecode.FORBIDDEN, "edit of live property")
+
+    def test_PROPPATCH_exists_not(self):
+        """
+        PROPPATCH remove a non-existant property
+        """
+        prop = davxml.Timeout() # Timeout isn't a valid property, so it won't exist.
+        patch = davxml.PropertyUpdate(davxml.Remove(davxml.PropertyContainer(prop)))
+
+        return self._simple_PROPPATCH(patch, prop, responsecode.OK, "remove of non-existant property")
+
+    def _simple_PROPPATCH(self, patch, prop, expected_code, what):
+        def check_result(response):
+            response = IResponse(response)
+
+            if response.code != responsecode.MULTI_STATUS:
+                self.fail("Incorrect response code for PROPPATCH (%s != %s)"
+                          % (response.code, responsecode.MULTI_STATUS))
+
+            return davXMLFromStream(response.stream).addCallback(check_xml)
+
+        def check_xml(doc):
+            response = doc.root_element.childOfType(davxml.Response)
+            propstat = response.childOfType(davxml.PropertyStatus)
+
+            self.failUnless(
+                response.childOfType(davxml.HRef) == "/",
+                "Incorrect response URI: %s != /" % (response.childOfType(davxml.HRef),)
+            )
+
+            self.failIf(
+                propstat.childOfType(davxml.PropertyContainer).childOfType(prop) is None,
+                "Not a %s in PROPPATCH property status: %s" % (prop.sname(), propstat.toxml())
+            )
+
+            self.failUnless(
+                propstat.childOfType(davxml.Status).code == expected_code,
+                "Incorrect status code for PROPPATCH %s: %s != %s"
+                % (what, propstat.childOfType(davxml.Status).code, expected_code)
+            )
+
+        request = SimpleRequest(self.site, "PROPPATCH", "/")
+        request.stream = MemoryStream(patch.toxml())
+        return self.send(request, check_result)
+
+class SpiffyProperty (davxml.WebDAVTextElement):
+    namespace = "http://twistedmatrix.com/ns/private/tests"
+    name = "spiffyproperty"

Copied: CalendarServer/trunk/twext/web2/dav/test/test_put.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_put.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_put.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_put.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,148 @@
+##
+# 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
+##
+
+import os
+import filecmp
+
+from twext.web2 import responsecode
+from twext.web2.iweb import IResponse
+from twext.web2.stream import FileStream
+from twext.web2.http import HTTPError
+
+import twext.web2.dav.test.util
+from twext.web2.test.test_server import SimpleRequest
+from twext.web2.dav.test.util import serialize
+
+class PUT(twext.web2.dav.test.util.TestCase):
+    """
+    PUT request
+    """
+    def test_PUT_simple(self):
+        """
+        PUT request
+        """
+        dst_path = os.path.join(self.docroot, "dst")
+
+        def checkResult(response, path):
+            response = IResponse(response)
+
+            if response.code not in (
+                responsecode.CREATED,
+                responsecode.NO_CONTENT
+            ):
+                self.fail("PUT failed: %s" % (response.code,))
+
+            if not os.path.isfile(dst_path):
+                self.fail("PUT failed to create file %s." % (dst_path,))
+
+            if not filecmp.cmp(path, dst_path):
+                self.fail("PUT failed to preserve data for file %s in file %s." % (path, dst_path))
+
+            etag = response.headers.getHeader("etag")
+            if not etag:
+                self.fail("No etag header in PUT response %r." % (response,))
+
+        #
+        # We need to serialize these request & test iterations because they can
+        # interfere with each other.
+        #
+        def work():
+            dst_uri = "/dst"
+
+            for name in os.listdir(self.docroot):
+                if name == "dst":
+                    continue
+
+                path = os.path.join(self.docroot, name)
+
+                # Can't really PUT something you can't read
+                if not os.path.isfile(path): continue
+    
+                def do_test(response): checkResult(response, path)
+    
+                request = SimpleRequest(self.site, "PUT", dst_uri)
+                request.stream = FileStream(file(path, "rb"))
+    
+                yield (request, do_test)
+
+        return serialize(self.send, work())
+
+    def test_PUT_again(self):
+        """
+        PUT on existing resource with If-None-Match header
+        """
+        dst_path = os.path.join(self.docroot, "dst")
+        dst_uri = "/dst"
+
+        def work():
+            for code in (
+                responsecode.CREATED,
+                responsecode.PRECONDITION_FAILED,
+                responsecode.NO_CONTENT,
+                responsecode.PRECONDITION_FAILED,
+                responsecode.NO_CONTENT,
+                responsecode.CREATED,
+            ):
+                def checkResult(response, code=code):
+                    response = IResponse(response)
+
+                    if response.code != code:
+                        self.fail("Incorrect response code for PUT (%s != %s)"
+                                  % (response.code, code))
+
+                def onError(f):
+                    f.trap(HTTPError)
+                    return checkResult(f.value.response)
+
+                request = SimpleRequest(self.site, "PUT", dst_uri)
+                request.stream = FileStream(file(__file__, "rb"))
+    
+                if code == responsecode.CREATED:
+                    if os.path.isfile(dst_path):
+                        os.remove(dst_path)
+                    request.headers.setHeader("if-none-match", ("*",))
+                elif code == responsecode.PRECONDITION_FAILED:
+                    request.headers.setHeader("if-none-match", ("*",))
+    
+                yield (request, (checkResult, onError))
+
+        return serialize(self.send, work())
+
+    def test_PUT_no_parent(self):
+        """
+        PUT with no parent
+        """
+        dst_uri = "/put/no/parent"
+
+        def checkResult(response):
+            response = IResponse(response)
+
+            if response.code != responsecode.CONFLICT:
+                self.fail("Incorrect response code for PUT with no parent (%s != %s)"
+                          % (response.code, responsecode.CONFLICT))
+
+        request = SimpleRequest(self.site, "PUT", dst_uri)
+        request.stream = FileStream(file(__file__, "rb"))
+
+        return self.send(request, checkResult)

Copied: CalendarServer/trunk/twext/web2/dav/test/test_quota.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_quota.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_quota.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_quota.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -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 twext.web2 import responsecode
+from twext.web2.iweb import IResponse
+from twext.web2.stream import FileStream
+
+import twext.web2.dav.test.util
+from twext.web2.test.test_server import SimpleRequest
+from twext.web2.dav.test.util import Site
+from twext.web2.dav import davxml
+import os
+
+class QuotaBase(twext.web2.dav.test.util.TestCase):
+
+    def createDocumentRoot(self):
+        docroot = self.mktemp()
+        os.mkdir(docroot)
+        rootresource = self.resource_class(docroot)
+        rootresource.setAccessControlList(self.grantInherit(davxml.All()))
+        self.site = Site(rootresource)
+        self.site.resource.setQuotaRoot(None, 100000)
+        return docroot
+
+
+    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/twext/web2/dav/test/test_report.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_report.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_report.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_report.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,71 @@
+##
+# 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 twext.web2.iweb import IResponse
+from twext.web2.stream import MemoryStream
+from twext.web2 import responsecode
+
+import twext.web2.dav.test.util
+from twext.web2.test.test_server import SimpleRequest
+from twext.web2.dav import davxml
+
+class REPORT(twext.web2.dav.test.util.TestCase):
+    """
+    REPORT request
+    """
+    def test_REPORT_no_body(self):
+        """
+        REPORT request with no body
+        """
+        def do_test(response):
+            response = IResponse(response)
+
+            if response.code != responsecode.BAD_REQUEST:
+                self.fail("Unexpected response code for REPORT with no body: %s"
+                          % (response.code,))
+
+        request = SimpleRequest(self.site, "REPORT", "/")
+        request.stream = MemoryStream("")
+
+        return self.send(request, do_test)
+
+    def test_REPORT_unknown(self):
+        """
+        Unknown/bogus report type
+        """
+        def do_test(response):
+            response = IResponse(response)
+
+            if response.code != responsecode.FORBIDDEN:
+                self.fail("Unexpected response code for unknown REPORT: %s"
+                          % (response.code,))
+        class GoofyReport (davxml.WebDAVUnknownElement):
+            namespace = "GOOFY:"
+            name      = "goofy-report"
+            def __init__(self): super(GoofyReport, self).__init__()
+
+        request = SimpleRequest(self.site, "REPORT", "/")
+        request.stream = MemoryStream(GoofyReport().toxml())
+
+        return self.send(request, do_test)

Copied: CalendarServer/trunk/twext/web2/dav/test/test_report_expand.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_report_expand.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_report_expand.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_report_expand.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,37 @@
+##
+# 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.trial.unittest import SkipTest
+
+import twext.web2.dav.test.util
+
+class REPORT_expand(twext.web2.dav.test.util.TestCase):
+    """
+    DAV:expand-property REPORT request
+    """
+    def test_REPORT_expand_property(self):
+        """
+        DAV:expand-property REPORT request.
+        """
+        raise SkipTest("test unimplemeted")

Copied: CalendarServer/trunk/twext/web2/dav/test/test_resource.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_resource.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_resource.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_resource.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,505 @@
+##
+# 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.defer import DeferredList, waitForDeferred, deferredGenerator, succeed
+from twisted.cred.portal import Portal
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError
+from twext.web2.auth import basic
+from twext.web2.server import Site
+from twext.web2.dav import davxml
+from twext.web2.dav.resource import DAVResource, AccessDeniedError, \
+    DAVPrincipalResource, DAVPrincipalCollectionResource, davPrivilegeSet
+from twext.web2.dav.auth import TwistedPasswordProperty, DavRealm, TwistedPropertyChecker, IPrincipal, AuthenticationWrapper
+from twext.web2.test.test_server import SimpleRequest
+from twext.web2.dav.test.util import InMemoryPropertyStore
+import twext.web2.dav.test.util
+
+class TestCase(twext.web2.dav.test.util.TestCase):
+    def setUp(self):
+        twext.web2.dav.test.util.TestCase.setUp(self)
+        TestResource._cachedPropertyStores = {}
+
+class GenericDAVResource(TestCase):
+    def setUp(self):
+        TestCase.setUp(self)
+
+        rootresource = TestResource(None, {
+            "file1": TestResource("/file1"),
+            "file2": AuthAllResource("/file2"),
+            "dir1": TestResource("/dir1/", {
+                "subdir1": TestResource("/dir1/subdir1/",{})
+            }),
+            "dir2": AuthAllResource("/dir2/", {
+                "file1": TestResource("/dir2/file1"),
+                "file2": TestResource("/dir2/file2"),
+                "subdir1": TestResource("/dir2/subdir1/", {
+                    "file1": TestResource("/dir2/subdir1/file1"),
+                    "file2": TestResource("/dir2/subdir1/file2")
+                })
+            })
+        })
+
+        self.site = Site(rootresource)
+
+    def test_findChildren(self):
+        """
+        This test asserts that we have:
+        1) not found any unexpected children
+        2) found all expected children
+
+        It does this for all depths C{"0"}, C{"1"}, and C{"infintiy"}
+        """
+        expected_children = {
+            "0": [],
+            "1": [
+                "/file1",
+                "/file2",
+                "/dir1/",
+                "/dir2/",
+            ],
+            "infinity": [
+                "/file1",
+                "/file2",
+                "/dir1/",
+                "/dir1/subdir1/",
+                "/dir2/",
+                "/dir2/file1",
+                "/dir2/file2",
+                "/dir2/subdir1/",
+                "/dir2/subdir1/file1",
+                "/dir2/subdir1/file2",
+            ],
+        }
+
+        request = SimpleRequest(self.site, "GET", "/")
+        resource = waitForDeferred(request.locateResource("/"))
+        yield resource
+        resource = resource.getResult()
+
+        def checkChildren(resource, uri):
+            self.assertEquals(uri, resource.uri)
+
+            if uri not in expected_children[depth]:
+                unexpected_children.append(uri)
+
+            else:
+                found_children.append(uri)
+
+        for depth in ["0", "1", "infinity"]:
+            found_children = []
+            unexpected_children = []
+
+            fc = resource.findChildren(depth, request, checkChildren)
+            completed = waitForDeferred(fc)
+            yield completed
+            completed.getResult()
+
+            self.assertEquals(
+                unexpected_children, [],
+                "Found unexpected children: %r" % (unexpected_children,)
+            )
+
+            expected_children[depth].sort()
+            found_children.sort()
+
+            self.assertEquals(expected_children[depth], found_children)
+
+    test_findChildren = deferredGenerator(test_findChildren)
+
+    def test_findChildrenWithPrivileges(self):
+        """
+        This test revokes read privileges for the C{"/file2"} and C{"/dir2/"}
+        resource to verify that we can not find them giving our unauthenticated
+        privileges.
+        """
+        
+        expected_children = [
+            "/file1",
+            "/dir1/",
+        ]
+
+        request = SimpleRequest(self.site, "GET", "/")
+        resource = waitForDeferred(request.locateResource("/"))
+        yield resource
+        resource = resource.getResult()
+
+        def checkChildren(resource, uri):
+            self.assertEquals(uri, resource.uri)
+
+            if uri not in expected_children:
+                unexpected_children.append(uri)
+            else:
+                found_children.append(uri)
+
+        found_children = []
+        unexpected_children = []
+
+        privileges = waitForDeferred(resource.currentPrivileges(request))
+        yield privileges
+        privileges = privileges.getResult()
+
+        fc = resource.findChildren("1", request, checkChildren, privileges)
+        completed = waitForDeferred(fc)
+        yield completed
+        completed.getResult()
+
+        self.assertEquals(
+            unexpected_children, [],
+            "Found unexpected children: %r" % (unexpected_children,)
+        )
+
+        expected_children.sort()
+        found_children.sort()
+
+        self.assertEquals(expected_children, found_children)
+
+    test_findChildrenWithPrivileges = deferredGenerator(test_findChildrenWithPrivileges)
+
+    def test_findChildrenCallbackRaises(self):
+        """
+        Verify that when the user callback raises an exception
+        the completion deferred returned by findChildren errbacks
+
+        TODO: Verify that the user callback doesn't get called subsequently
+        """
+
+        def raiseOnChild(resource, uri):
+            raise Exception("Oh no!")
+
+        def findChildren(resource):
+            return self.assertFailure(
+                resource.findChildren("infinity", request, raiseOnChild),
+                Exception
+            )
+        
+        request = SimpleRequest(self.site, "GET", "/")
+        d = request.locateResource("/").addCallback(findChildren)
+
+        return d
+
+class AccessTests(TestCase):
+    def setUp(self):
+        TestCase.setUp(self)
+
+        gooduser = TestDAVPrincipalResource("/users/gooduser")
+        gooduser.writeDeadProperty(TwistedPasswordProperty("goodpass"))
+
+        baduser = TestDAVPrincipalResource("/users/baduser")
+        baduser.writeDeadProperty(TwistedPasswordProperty("badpass"))
+
+        rootresource = TestPrincipalsCollection("/", {
+                "users": TestResource("/users/",
+                                      {"gooduser": gooduser,
+                                       "baduser": baduser})
+            })
+
+        protected = TestResource(
+            "/protected", principalCollections=[rootresource])
+
+        protected.setAccessControlList(davxml.ACL(
+            davxml.ACE(
+                davxml.Principal(davxml.HRef("/users/gooduser")),
+                davxml.Grant(davxml.Privilege(davxml.All())),
+                davxml.Protected()
+            )
+        ))
+
+        rootresource.children["protected"] = protected
+
+        portal = Portal(DavRealm())
+        portal.registerChecker(TwistedPropertyChecker())
+
+        credentialFactories = (basic.BasicCredentialFactory(""),)
+
+        loginInterfaces = (IPrincipal,)
+
+        self.rootresource = rootresource
+        self.site = Site(AuthenticationWrapper(
+            self.rootresource,
+            portal,
+            credentialFactories,
+            loginInterfaces,
+        ))
+
+    def checkSecurity(self, request):
+        """
+        Locate the resource named by the given request's URI, then authorize it
+        for the 'Read' permission.
+        """
+        d = request.locateResource(request.uri)
+        d.addCallback(lambda r: r.authorize(request, (davxml.Read(),)))
+        return d
+
+    def assertErrorResponse(self, error, expectedcode, otherExpectations=lambda err: None):
+        self.assertEquals(error.response.code, expectedcode)
+        otherExpectations(error)
+
+    def test_checkPrivileges(self):
+        """
+        DAVResource.checkPrivileges()
+        """
+        ds = []
+
+        authAllResource = AuthAllResource()
+        requested_access = (davxml.All(),)
+
+        site = Site(authAllResource)
+
+        def expectError(failure):
+            failure.trap(AccessDeniedError)
+            errors = failure.value.errors
+
+            self.failUnless(len(errors) == 1)
+
+            subpath, denials = errors[0]
+
+            self.failUnless(subpath is None)
+            self.failUnless(
+                tuple(denials) == requested_access,
+                "%r != %r" % (tuple(denials), requested_access)
+            )
+
+        def expectOK(result):
+            self.failUnlessEquals(result, None)
+
+        def _checkPrivileges(resource):
+            d = resource.checkPrivileges(request, requested_access)
+            return d
+
+        # No auth; should deny
+        request = SimpleRequest(site, "GET", "/")
+        d = request.locateResource("/").addCallback(_checkPrivileges).addErrback(expectError)
+        ds.append(d)
+
+        # Has auth; should allow
+        request = SimpleRequest(site, "GET", "/")
+        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)
+        ds.append(d)
+
+        return DeferredList(ds)
+
+
+    def test_authorize(self):
+        """
+        Authorizing a known user with the correct password will not raise an
+        exception, indicating that the user is properly authorized given their
+        credentials.
+        """
+        request = SimpleRequest(self.site, "GET", "/protected")
+        request.headers.setHeader(
+            "authorization",
+            ("basic", "gooduser:goodpass".encode("base64")))
+        return self.checkSecurity(request)
+
+    def test_badUsernameOrPassword(self):
+        request = SimpleRequest(self.site, "GET", "/protected")
+        request.headers.setHeader(
+            "authorization",
+            ("basic", "gooduser:badpass".encode("base64"))
+        )
+        d = self.assertFailure(self.checkSecurity(request), HTTPError)
+        def expectWwwAuth(err):
+            self.failUnless(err.response.headers.hasHeader("WWW-Authenticate"),
+                            "No WWW-Authenticate header present.")
+        d.addCallback(self.assertErrorResponse, responsecode.UNAUTHORIZED, expectWwwAuth)
+        return d
+
+
+    def test_lacksPrivileges(self):
+        request = SimpleRequest(self.site, "GET", "/protected")
+        request.headers.setHeader(
+            "authorization",
+            ("basic", "baduser:badpass".encode("base64"))
+        )
+        d = self.assertFailure(self.checkSecurity(request), HTTPError)
+        d.addCallback(self.assertErrorResponse, responsecode.FORBIDDEN)
+        return d
+
+
+##
+# Utilities
+##
+
+class TestResource (DAVResource):
+    """A simple test resource used for creating trees of
+    DAV Resources
+    """
+    _cachedPropertyStores = {}
+
+    acl = davxml.ACL(
+        davxml.ACE(
+            davxml.Principal(davxml.All()),
+            davxml.Grant(davxml.Privilege(davxml.All())),
+            davxml.Protected(),
+        )
+    )
+
+    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
+
+    def deadProperties(self):
+        """
+        Retrieve deadProperties from a special place in memory
+        """
+        if not hasattr(self, "_dead_properties"):
+            dp = TestResource._cachedPropertyStores.get(self.uri)
+            if dp is None:
+                TestResource._cachedPropertyStores[self.uri] = InMemoryPropertyStore(self)
+                dp = TestResource._cachedPropertyStores[self.uri]
+            self._dead_properties = dp
+        return self._dead_properties
+
+    def isCollection(self):
+        return self.children is not None
+
+    def listChildren(self):
+        return self.children.keys()
+
+    def supportedPrivileges(self, request):
+        return succeed(davPrivilegeSet)
+
+    def currentPrincipal(self, request):
+        if hasattr(request, "authzUser"):
+            return request.authzUser
+        else:
+            return davxml.Principal(davxml.Unauthenticated())
+
+    def locateChild(self, request, segments):
+        child = segments[0]
+        if child == "":
+            return self, segments[1:]
+        elif child in self.children:
+            return self.children[child], segments[1:]
+        else:
+            raise HTTPError(404)
+
+    def setAccessControlList(self, acl):
+        self.acl = acl
+
+    def accessControlList(self, request, **kwargs):
+        return succeed(self.acl)
+
+
+
+class TestPrincipalsCollection(DAVPrincipalCollectionResource, TestResource):
+    """
+    A full implementation of L{IDAVPrincipalCollectionResource}, implemented as
+    a L{TestResource} which assumes a single L{TestResource} child named
+    'users'.
+    """
+
+    def __init__(self, url, children):
+        DAVPrincipalCollectionResource.__init__(self, url)
+        TestResource.__init__(self, url, children, principalCollections=(self,))
+
+
+    def principalForUser(self, user):
+        """
+        @see L{IDAVPrincipalCollectionResource.principalForUser}.
+        """
+        return self.principalForShortName('users', user)
+
+
+    def principalForAuthID(self, creds):
+        """
+        Retrieve the principal for the authentication identifier from a set of
+        credentials.
+
+        Note that although this method is not actually invoked anywhere in
+        web2.dav, this test class is currently imported by CalendarServer,
+        which requires this method.
+
+        @param creds: credentials which identify a user
+
+        @type creds: L{twisted.cred.credentials.IUsernameHashedPassword} or
+            L{twisted.cred.credentials.IUsernamePassword}
+
+        @return: a DAV principal resource representing a user.
+
+        @rtype: L{IDAVPrincipalResource} or C{NoneType}
+        """
+        # XXX either move this to CalendarServer entirely or document it on
+        # IDAVPrincipalCollectionResource
+        return self.principalForShortName('users', creds.username)
+
+
+    def principalForShortName(self, type, shortName):
+        """
+        Retrieve the principal of a given type from this resource.
+
+        Note that although this method is not actually invoked anywhere (aside
+        from test methods) in web2.dav, this test class is currently imported by
+        CalendarServer, which requires this method.
+
+        @param: a short string (such as 'users' or 'groups') identifying both
+            the principal type, and the name of a resource in the 'children'
+            dictionary, which itself is a L{TestResource} with
+            L{IDAVPrincipalCollectionResource} children.
+
+        @return: a DAV principal resource of the given type with the given
+            name.
+
+        @rtype: L{IDAVPrincipalResource} or C{NoneType}
+        """
+        # XXX either move this to CalendarServer entirely or document it on
+        # IDAVPrincipalCollectionResource
+        typeResource = self.children.get(type, None)
+        user = None
+        if typeResource:
+            user = typeResource.children.get(shortName, None)
+
+        return user
+
+
+
+class AuthAllResource (TestResource):
+    """
+    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(),
+        )
+    )
+
+    
+class TestDAVPrincipalResource(DAVPrincipalResource, TestResource):
+    """
+    Get deadProperties from TestResource
+    """
+    def principalURL(self):
+        return self.uri

Copied: CalendarServer/trunk/twext/web2/dav/test/test_static.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_static.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_static.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_static.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,62 @@
+##
+# 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 twext.web2.dav.test import util
+from twext.web2.dav import davxml
+from twext.web2.stream import readStream
+from twext.web2.test.test_server import SimpleRequest
+
+class DAVFileTest(util.TestCase):
+    def test_renderPrivileges(self):
+        """
+        Verify that a directory listing includes children which you
+        don't have access to.
+        """
+        request = SimpleRequest(self.site, "GET", "/")
+
+        def setEmptyACL(resource):
+            resource.setAccessControlList(davxml.ACL()) # Empty ACL = no access
+            return resource
+
+        def renderRoot(_):
+            d = request.locateResource("/")
+            d.addCallback(lambda r: r.render(request))
+
+            return d
+
+        def assertListing(response):
+            data = []
+            d = readStream(response.stream, lambda s: data.append(str(s)))
+            d.addCallback(lambda _: self.failIf(
+                'href="dir2/"' not in "".join(data),
+                "'dir2' expected in listing: %r" % (data,)
+            ))
+            return d
+
+        d = request.locateResource("/dir2")
+        d.addCallback(setEmptyACL)
+        d.addCallback(renderRoot)
+        d.addCallback(assertListing)
+
+        return d

Copied: CalendarServer/trunk/twext/web2/dav/test/test_stream.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_stream.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_stream.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_stream.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,129 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+##
+# 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.python.hashlib import md5
+from twisted.internet.defer import Deferred
+from twisted.trial.unittest import TestCase
+from twext.web2.stream import MemoryStream
+from twext.web2.dav.stream import MD5StreamWrapper
+
+
+class AsynchronousDummyStream(object):
+    """
+    An L{IByteStream} implementation which always returns a L{Deferred} from
+    C{read} and lets an external driver fire them.
+    """
+    def __init__(self):
+        self._readResults = []
+
+
+    def read(self):
+        result = Deferred()
+        self._readResults.append(result)
+        return result
+
+
+    def _write(self, bytes):
+        self._readResults.pop(0).callback(bytes)
+
+
+
+class MD5StreamWrapperTests(TestCase):
+    """
+    Tests for L{MD5StreamWrapper}.
+    """
+    data = "I am sorry Dave, I can't do that.\n--HAL 9000"
+    digest = md5(data).hexdigest()
+
+    def test_synchronous(self):
+        """
+        L{MD5StreamWrapper} computes the MD5 hash of the contents of the stream
+        around which it is wrapped.  It supports L{IByteStream} providers which
+        return C{str} from their C{read} method.
+        """
+        dataStream = MemoryStream(self.data)
+        md5Stream = MD5StreamWrapper(dataStream)
+
+        self.assertEquals(str(md5Stream.read()), self.data)
+        self.assertIdentical(md5Stream.read(), None)
+        md5Stream.close()
+
+        self.assertEquals(self.digest, md5Stream.getMD5())
+
+
+    def test_asynchronous(self):
+        """
+        L{MD5StreamWrapper} also supports L{IByteStream} providers which return
+        L{Deferreds} from their C{read} method.
+        """
+        dataStream = AsynchronousDummyStream()
+        md5Stream = MD5StreamWrapper(dataStream)
+
+        result = md5Stream.read()
+        dataStream._write(self.data)
+        result.addCallback(self.assertEquals, self.data)
+
+        def cbRead(ignored):
+            result = md5Stream.read()
+            dataStream._write(None)
+            result.addCallback(self.assertIdentical, None)
+            return result
+        result.addCallback(cbRead)
+
+        def cbClosed(ignored):
+            md5Stream.close()
+            self.assertEquals(md5Stream.getMD5(), self.digest)
+        result.addCallback(cbClosed)
+
+        return result
+
+
+    def test_getMD5FailsBeforeClose(self):
+        """
+        L{MD5StreamWrapper.getMD5} raises L{RuntimeError} if called before
+        L{MD5StreamWrapper.close}.
+        """
+        dataStream = MemoryStream(self.data)
+        md5Stream = MD5StreamWrapper(dataStream)
+        self.assertRaises(RuntimeError, md5Stream.getMD5)
+
+
+    def test_initializationFailsWithoutStream(self):
+        """
+        L{MD5StreamWrapper.__init__} raises L{ValueError} if passed C{None} as
+        the stream to wrap.
+        """
+        self.assertRaises(ValueError, MD5StreamWrapper, None)
+
+
+    def test_readAfterClose(self):
+        """
+        L{MD5StreamWrapper.read} raises L{RuntimeError} if called after
+        L{MD5StreamWrapper.close}.
+        """
+        dataStream = MemoryStream(self.data)
+        md5Stream = MD5StreamWrapper(dataStream)
+        md5Stream.close()
+        self.assertRaises(RuntimeError, md5Stream.read)

Copied: CalendarServer/trunk/twext/web2/dav/test/test_util.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_util.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_util.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_util.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,96 @@
+##
+# 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.trial import unittest
+from twext.web2.dav import util
+
+class Utilities(unittest.TestCase):
+    """
+    Utilities.
+    """
+    def test_normalizeURL(self):
+        """
+        normalizeURL()
+        """
+        self.assertEquals(util.normalizeURL("http://server//foo"), "http://server/foo")
+        self.assertEquals(util.normalizeURL("http://server/foo/.."), "http://server/")
+        self.assertEquals(util.normalizeURL("/foo/bar/..//"), "/foo")
+        self.assertEquals(util.normalizeURL("/foo/bar/.//"), "/foo/bar")
+        self.assertEquals(util.normalizeURL("//foo///bar/../baz"), "/foo/baz")
+        self.assertEquals(util.normalizeURL("//foo///bar/./baz"), "/foo/bar/baz")
+        self.assertEquals(util.normalizeURL("///../"), "/")
+        self.assertEquals(util.normalizeURL("/.."), "/")
+
+    def test_joinURL(self):
+        """
+        joinURL()
+        """
+        self.assertEquals(util.joinURL("http://server/foo/"), "http://server/foo/")
+        self.assertEquals(util.joinURL("http://server/foo", "/bar"), "http://server/foo/bar")
+        self.assertEquals(util.joinURL("http://server/foo", "bar"), "http://server/foo/bar")
+        self.assertEquals(util.joinURL("http://server/foo/", "/bar"), "http://server/foo/bar")
+        self.assertEquals(util.joinURL("http://server/foo/", "/bar/.."), "http://server/foo")
+        self.assertEquals(util.joinURL("http://server/foo/", "/bar/."), "http://server/foo/bar")
+        self.assertEquals(util.joinURL("http://server/foo/", "/bar/../"), "http://server/foo/")
+        self.assertEquals(util.joinURL("http://server/foo/", "/bar/./"), "http://server/foo/bar/")
+        self.assertEquals(util.joinURL("http://server/foo/../", "/bar"), "http://server/bar")
+        self.assertEquals(util.joinURL("/foo/"), "/foo/")
+        self.assertEquals(util.joinURL("/foo", "/bar"), "/foo/bar")
+        self.assertEquals(util.joinURL("/foo", "bar"), "/foo/bar")
+        self.assertEquals(util.joinURL("/foo/", "/bar"), "/foo/bar")
+        self.assertEquals(util.joinURL("/foo/", "/bar/.."), "/foo")
+        self.assertEquals(util.joinURL("/foo/", "/bar/."), "/foo/bar")
+        self.assertEquals(util.joinURL("/foo/", "/bar/../"), "/foo/")
+        self.assertEquals(util.joinURL("/foo/", "/bar/./"), "/foo/bar/")
+        self.assertEquals(util.joinURL("/foo/../", "/bar"), "/bar")
+        self.assertEquals(util.joinURL("/foo", "/../"), "/")
+        self.assertEquals(util.joinURL("/foo", "/./"), "/foo/")
+
+    def test_parentForURL(self):
+        """
+        parentForURL()
+        """
+        self.assertEquals(util.parentForURL("http://server/"), None)
+        self.assertEquals(util.parentForURL("http://server//"), None)
+        self.assertEquals(util.parentForURL("http://server/foo/.."), None)
+        self.assertEquals(util.parentForURL("http://server/foo/../"), None)
+        self.assertEquals(util.parentForURL("http://server/foo/."), "http://server/")
+        self.assertEquals(util.parentForURL("http://server/foo/./"), "http://server/")
+        self.assertEquals(util.parentForURL("http://server/foo"), "http://server/")
+        self.assertEquals(util.parentForURL("http://server//foo"), "http://server/")
+        self.assertEquals(util.parentForURL("http://server/foo/bar/.."), "http://server/")
+        self.assertEquals(util.parentForURL("http://server/foo/bar/."), "http://server/foo/")
+        self.assertEquals(util.parentForURL("http://server/foo/bar"), "http://server/foo/")
+        self.assertEquals(util.parentForURL("http://server/foo/bar/"), "http://server/foo/")
+        self.assertEquals(util.parentForURL("/"), None)
+        self.assertEquals(util.parentForURL("/foo/.."), None)
+        self.assertEquals(util.parentForURL("/foo/../"), None)
+        self.assertEquals(util.parentForURL("/foo/."), "/")
+        self.assertEquals(util.parentForURL("/foo/./"), "/")
+        self.assertEquals(util.parentForURL("/foo"), "/")
+        self.assertEquals(util.parentForURL("/foo"), "/")
+        self.assertEquals(util.parentForURL("/foo/bar/.."), "/")
+        self.assertEquals(util.parentForURL("/foo/bar/."), "/foo/")
+        self.assertEquals(util.parentForURL("/foo/bar"), "/foo/")
+        self.assertEquals(util.parentForURL("/foo/bar/"), "/foo/")

Copied: CalendarServer/trunk/twext/web2/dav/test/test_xattrprops.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_xattrprops.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_xattrprops.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_xattrprops.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,308 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twext.web2.dav.xattrprops}.
+"""
+
+from zlib import compress, decompress
+from pickle import dumps
+from cPickle import UnpicklingError
+
+from twext.python.filepath import CachingFilePath as FilePath
+from twisted.trial.unittest import TestCase
+from twext.web2.responsecode import NOT_FOUND, INTERNAL_SERVER_ERROR
+from twext.web2.responsecode import FORBIDDEN
+from twext.web2.http import HTTPError
+from twext.web2.dav.static import DAVFile
+from twext.web2.dav.davxml import Depth, WebDAVDocument
+
+try:
+    from twext.web2.dav.xattrprops import xattrPropertyStore
+except ImportError:
+    xattrPropertyStore = None
+else:
+    from xattr import xattr
+
+class ExtendedAttributesPropertyStoreTests(TestCase):
+    """
+    Tests for L{xattrPropertyStore}.
+    """
+    if xattrPropertyStore is None:
+        skip = "xattr package missing, cannot test xattr property store"
+
+    def setUp(self):
+        """
+        Create a resource and a xattr property store for it.
+        """
+        self.resourcePath = FilePath(self.mktemp())
+        self.resourcePath.setContent("")
+        self.attrs = xattr(self.resourcePath.path)
+        self.resource = DAVFile(self.resourcePath.path)
+        self.propertyStore = xattrPropertyStore(self.resource)
+
+
+    def test_getAbsent(self):
+        """
+        L{xattrPropertyStore.get} raises L{HTTPError} with a I{NOT FOUND}
+        response code if passed the name of an attribute for which there is no
+        corresponding value.
+        """
+        error = self.assertRaises(HTTPError, self.propertyStore.get, ("foo", "bar"))
+        self.assertEquals(error.response.code, NOT_FOUND)
+
+
+    def _forbiddenTest(self, method):
+        # Remove access to the directory containing the file so that getting
+        # extended attributes from it fails with EPERM.
+        self.resourcePath.parent().chmod(0)
+        # Make sure to restore access to it later so that it can be deleted
+        # after the test run is finished.
+        self.addCleanup(self.resourcePath.parent().chmod, 0700)
+
+        # Try to get a property from it - and fail.
+        document = self._makeValue()
+        error = self.assertRaises(
+            HTTPError,
+            getattr(self.propertyStore, method),
+            document.root_element.qname())
+
+        # Make sure that the status is FORBIDDEN, a roughly reasonable mapping
+        # of the EPERM failure.
+        self.assertEquals(error.response.code, FORBIDDEN)
+
+
+    def test_getErrors(self):
+        """
+        If there is a problem getting the specified property (aside from the
+        property not existing), L{xattrPropertyStore.get} raises L{HTTPError}
+        with a status code which is determined by the nature of the problem.
+        """
+        self._forbiddenTest('get')
+
+
+    def _makeValue(self):
+        """
+        Create and return any old WebDAVDocument for use by the get tests.
+        """
+        element = Depth("0")
+        document = WebDAVDocument(element)
+        return document
+
+
+    def _setValue(self, originalDocument, value):
+        element = originalDocument.root_element
+        attribute = (
+            self.propertyStore.deadPropertyXattrPrefix +
+            "{%s}%s" % element.qname())
+        self.attrs[attribute] = value
+
+
+    def _getValue(self, originalDocument):
+        element = originalDocument.root_element
+        attribute = (
+            self.propertyStore.deadPropertyXattrPrefix +
+            "{%s}%s" % element.qname())
+        return self.attrs[attribute]
+
+
+    def _checkValue(self, originalDocument):
+        property = originalDocument.root_element.qname()
+
+        # Try to load it via xattrPropertyStore.get
+        loadedDocument = self.propertyStore.get(property)
+
+        # XXX Why isn't this a WebDAVDocument?
+        self.assertIsInstance(loadedDocument, Depth)
+        self.assertEquals(str(loadedDocument), "0")
+
+
+    def test_getXML(self):
+        """
+        If there is an XML document associated with the property name passed to
+        L{xattrPropertyStore.get}, that value is parsed into a
+        L{WebDAVDocument}, the root element of which C{get} then returns.
+        """
+        document = self._makeValue()
+        self._setValue(document, document.toxml())
+        self._checkValue(document)
+
+
+    def test_getCompressed(self):
+        """
+        If there is a compressed value associated with the property name passed
+        to L{xattrPropertyStore.get}, that value is decompressed and parsed
+        into a L{WebDAVDocument}, the root element of which C{get} then
+        returns.
+        """
+        document = self._makeValue()
+        self._setValue(document, compress(document.toxml()))
+        self._checkValue(document)
+
+
+    def test_getPickled(self):
+        """
+        If there is a pickled document associated with the property name passed
+        to L{xattrPropertyStore.get}, that value is unpickled into a
+        L{WebDAVDocument}, the root element of which is returned.
+        """
+        document = self._makeValue()
+        self._setValue(document, dumps(document))
+        self._checkValue(document)
+
+
+    def test_getUpgradeXML(self):
+        """
+        If the value associated with the property name passed to
+        L{xattrPropertyStore.get} is an uncompressed XML document, it is
+        upgraded on access by compressing it.
+        """
+        document = self._makeValue()
+        originalValue = document.toxml()
+        self._setValue(document, originalValue)
+        self._checkValue(document)
+        self.assertEquals(
+            decompress(self._getValue(document)), originalValue)
+
+
+    def test_getUpgradeCompressedPickle(self):
+        """
+        If the value associated with the property name passed to
+        L{xattrPropertyStore.get} is a compressed pickled document, it is
+        upgraded on access to the compressed XML format.
+        """
+        document = self._makeValue()
+        self._setValue(document, compress(dumps(document)))
+        self._checkValue(document)
+        self.assertEquals(
+            decompress(self._getValue(document)), document.toxml())
+
+
+    def test_getInvalid(self):
+        """
+        If the value associated with the property name passed to
+        L{xattrPropertyStore.get} cannot be interpreted, an error is logged and
+        L{HTTPError} is raised with the I{INTERNAL SERVER ERROR} response code.
+        """
+        document = self._makeValue()
+        self._setValue(
+            document,
+            "random garbage goes here! \0 that nul is definitely garbage")
+
+        property = document.root_element.qname()
+        error = self.assertRaises(HTTPError, self.propertyStore.get, property)
+        self.assertEquals(error.response.code, INTERNAL_SERVER_ERROR)
+        self.assertEquals(
+            len(self.flushLoggedErrors(UnpicklingError)), 1)
+
+
+    def test_set(self):
+        """
+        L{xattrPropertyStore.set} accepts a L{WebDAVElement} and stores a
+        compressed XML document representing it in an extended attribute.
+        """
+        document = self._makeValue()
+        self.propertyStore.set(document.root_element)
+        self.assertEquals(
+            decompress(self._getValue(document)), document.toxml())
+
+
+    def test_delete(self):
+        """
+        L{xattrPropertyStore.delete} deletes the named property.
+        """
+        document = self._makeValue()
+        self.propertyStore.set(document.root_element)
+        self.propertyStore.delete(document.root_element.qname())
+        self.assertRaises(KeyError, self._getValue, document)
+
+
+    def test_deleteNonExistent(self):
+        """
+        L{xattrPropertyStore.delete} does nothing if passed a property which
+        has no value.
+        """
+        document = self._makeValue()
+        self.propertyStore.delete(document.root_element.qname())
+        self.assertRaises(KeyError, self._getValue, document)
+
+
+    def test_deleteErrors(self):
+        """
+        If there is a problem deleting the specified property (aside from the
+        property not existing), L{xattrPropertyStore.delete} raises
+        L{HTTPError} with a status code which is determined by the nature of
+        the problem.
+        """
+        # Remove the file so that deleting extended attributes of it fails with
+        # EEXIST.
+        self.resourcePath.remove()
+
+        # Try to delete a property from it - and fail.
+        document = self._makeValue()
+        error = self.assertRaises(
+            HTTPError,
+            self.propertyStore.delete, document.root_element.qname())
+
+        # Make sure that the status is NOT FOUND, a roughly reasonable mapping
+        # of the EEXIST failure.
+        self.assertEquals(error.response.code, NOT_FOUND)
+
+
+    def test_contains(self):
+        """
+        L{xattrPropertyStore.contains} returns C{True} if the given property
+        has a value, C{False} otherwise.
+        """
+        document = self._makeValue()
+        self.assertFalse(
+            self.propertyStore.contains(document.root_element.qname()))
+        self._setValue(document, document.toxml())
+        self.assertTrue(
+            self.propertyStore.contains(document.root_element.qname()))
+
+
+    def test_containsError(self):
+        """
+        If there is a problem checking if the specified property exists (aside
+        from the property not existing), L{xattrPropertyStore.contains} raises
+        L{HTTPError} with a status code which is determined by the nature of
+        the problem.
+        """
+        self._forbiddenTest('contains')
+
+
+    def test_list(self):
+        """
+        L{xattrPropertyStore.list} returns a C{list} of property names
+        associated with the wrapped file.
+        """
+        prefix = self.propertyStore.deadPropertyXattrPrefix
+        self.attrs[prefix + '{foo}bar'] = 'baz'
+        self.attrs[prefix + '{bar}baz'] = 'quux'
+        self.assertEquals(
+            set(self.propertyStore.list()),
+            set([(u'foo', u'bar'), (u'bar', u'baz')]))
+
+
+    def test_listError(self):
+        """
+        If there is a problem checking if the specified property exists (aside
+        from the property not existing), L{xattrPropertyStore.contains} raises
+        L{HTTPError} with a status code which is determined by the nature of
+        the problem.
+        """
+        # Remove access to the directory containing the file so that getting
+        # extended attributes from it fails with EPERM.
+        self.resourcePath.parent().chmod(0)
+        # Make sure to restore access to it later so that it can be deleted
+        # after the test run is finished.
+        self.addCleanup(self.resourcePath.parent().chmod, 0700)
+
+        # Try to get a property from it - and fail.
+        document = self._makeValue()
+        error = self.assertRaises(HTTPError, self.propertyStore.list)
+
+        # Make sure that the status is FORBIDDEN, a roughly reasonable mapping
+        # of the EPERM failure.
+        self.assertEquals(error.response.code, FORBIDDEN)

Copied: CalendarServer/trunk/twext/web2/dav/test/test_xml.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_xml.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_xml.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_xml.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,105 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+##
+# 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.
+##
+
+"""
+Tests for L{twext.web2.dav.davxml}.
+"""
+
+from twisted.trial.unittest import TestCase
+from twext.web2.dav.davxml import WebDAVDocument, WebDAVUnknownElement
+from twext.web2.dav.davxml import Response, HRef, MultiStatus, Status
+from twext.web2.dav.davxml import CurrentUserPrincipal
+
+
+class WebDAVElementTestsMixin:
+    """
+    Mixin for L{TestCase}s which test a L{WebDAVElement} subclass.
+    """
+    def test_fromString(self):
+        """
+        The XML representation of L{WebDAVDocument} can be parsed into a
+        L{WebDAVDocument} instance using L{WebDAVDocument.fromString}.
+        """
+        doc = WebDAVDocument.fromString(self.serialized)
+        self.assertEquals(doc, WebDAVDocument(self.element))
+
+
+    def test_toxml(self):
+        """
+        L{WebDAVDocument.toxml} returns a C{str} giving the XML representation
+        of the L{WebDAVDocument} instance.
+        """
+        document = WebDAVDocument(self.element)
+        self.assertEquals(
+            document,
+            WebDAVDocument.fromString(document.toxml()))
+
+
+
+class MultiStatusTests(WebDAVElementTestsMixin, TestCase):
+    """
+    Tests for L{MultiStatus}
+    """
+    serialized = (
+        '<?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>')
+
+    element = MultiStatus(
+        Response(
+            HRef("http://webdav.sb.aol.com/webdav/secret"),
+            Status("HTTP/1.1 403 Forbidden")))
+
+
+
+class WebDAVUnknownElementTests(WebDAVElementTestsMixin, TestCase):
+    """
+    Tests for L{WebDAVUnknownElement}.
+    """
+    serialized = (
+        '<?xml version="1.0" encoding="utf-8" ?>'
+        '<T:foo xmlns:T="http://twistedmatrix.com/"/>')
+
+    element = WebDAVUnknownElement()
+    element.namespace = "http://twistedmatrix.com/"
+    element.name = "foo"
+
+
+
+class CurrentUserPrincipalTests(WebDAVElementTestsMixin, TestCase):
+    """
+    Tests for L{CurrentUserPrincipal}.
+    """
+    serialized = (
+        '<?xml version="1.0" encoding="utf-8" ?>'
+        '<D:current-user-principal xmlns:D="DAV:">'
+        '  <D:href>foo</D:href>'
+        '</D:current-user-principal>')
+
+    element = CurrentUserPrincipal(HRef("foo"))

Copied: CalendarServer/trunk/twext/web2/dav/test/test_xml_rfc3744.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/test_xml_rfc3744.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/test_xml_rfc3744.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/test_xml_rfc3744.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,59 @@
+##
+# 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.trial import unittest
+from twext.web2.dav import davxml
+from twext.web2.dav.resource import davPrivilegeSet
+
+class XML_3744(unittest.TestCase):
+    """
+    RFC 3744 (WebDAV ACL) XML tests.
+    """
+    def test_Privilege_isAggregateOf(self):
+        """
+        Privilege.isAggregateOf()
+        """
+        for a, b in (
+            (davxml.All(), davxml.Write()),
+            (davxml.All(), davxml.ReadACL()),
+            (davxml.Write(), davxml.WriteProperties()),
+            (davxml.Write(), davxml.WriteContent()),
+            (davxml.Write(), davxml.Bind()),
+            (davxml.Write(), davxml.Unbind()),
+        ):
+            pa = davxml.Privilege(a)
+            pb = davxml.Privilege(b)
+
+            self.failUnless(pa.isAggregateOf(pb, davPrivilegeSet), "%s contains %s" % (a.sname(), b.sname()))
+            self.failIf(pb.isAggregateOf(pa, davPrivilegeSet), "%s does not contain %s" % (b.sname(), a.sname()))
+
+        for a, b in (
+            (davxml.Unlock(), davxml.Write()),
+            (davxml.Unlock(), davxml.WriteACL()),
+            (davxml.ReadCurrentUserPrivilegeSet(), davxml.WriteProperties()),
+        ):
+            pa = davxml.Privilege(a)
+            pb = davxml.Privilege(b)
+
+            self.failIf(pb.isAggregateOf(pa, davPrivilegeSet), "%s does not contain %s" % (b.sname(), a.sname()))

Copied: CalendarServer/trunk/twext/web2/dav/test/tworequest_client.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/tworequest_client.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/tworequest_client.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/tworequest_client.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -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/twext/web2/dav/test/util.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/test/util.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/util.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/test/util.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,247 @@
+##
+# 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
+##
+
+import os
+from urllib import quote as url_quote
+from filecmp import dircmp as DirCompare
+from tempfile import mkdtemp
+from shutil import copy
+from random import randrange, choice
+
+from twisted.python import log
+from twisted.trial import unittest
+from twisted.internet.defer import Deferred
+
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.fileop import rmdir
+from twext.web2.dav.resource import TwistedACLInheritable
+from twext.web2.dav.static import DAVFile
+from twext.web2.dav.util import joinURL
+
+class InMemoryPropertyStore (object):
+    """
+    A dead property store for keeping properties in memory
+
+    DO NOT USE OUTSIDE OF UNIT TESTS!
+    """
+    def __init__(self, resource):
+        self._dict = {}
+
+    def get(self, qname):
+        try:
+            property = self._dict[qname]
+        except KeyError:
+            raise HTTPError(StatusResponse(
+                responsecode.NOT_FOUND,
+                "No such property: {%s}%s" % qname
+            ))
+
+        doc = davxml.WebDAVDocument.fromString(property)
+        return doc.root_element
+
+    def set(self, property):
+        self._dict[property.qname()] = property.toxml()
+
+    def delete(self, qname):
+        try:
+            del(self._dict[qname])
+        except KeyError:
+            pass
+
+    def contains(self, qname):
+        return qname in self._dict
+
+    def list(self):
+        return self._dict.keys()
+
+class TestFile (DAVFile):
+    _cachedPropertyStores = {}
+
+    def deadProperties(self):
+        if not hasattr(self, "_dead_properties"):
+            dp = TestFile._cachedPropertyStores.get(self.fp.path)
+            if dp is None:
+                TestFile._cachedPropertyStores[self.fp.path] = InMemoryPropertyStore(self)
+                dp = TestFile._cachedPropertyStores[self.fp.path]
+
+            self._dead_properties = dp
+
+        return self._dead_properties
+
+class TestCase (unittest.TestCase):
+    resource_class = TestFile
+
+    def grant(*privileges):
+        return davxml.ACL(*[
+            davxml.ACE(
+                davxml.Grant(davxml.Privilege(privilege)),
+                davxml.Principal(davxml.All())
+            )
+            for privilege in privileges
+        ])
+
+    grant = staticmethod(grant)
+
+    def grantInherit(*privileges):
+        return davxml.ACL(*[
+            davxml.ACE(
+                davxml.Grant(davxml.Privilege(privilege)),
+                davxml.Principal(davxml.All()),
+                TwistedACLInheritable()
+            )
+            for privilege in privileges
+        ])
+
+    grantInherit = staticmethod(grantInherit)
+
+    def createDocumentRoot(self):
+        docroot = self.mktemp()
+        os.mkdir(docroot)
+        rootresource = self.resource_class(docroot)
+        rootresource.setAccessControlList(self.grantInherit(davxml.All()))
+
+        dirnames = (
+            os.path.join(docroot, "dir1"),                          # 0
+            os.path.join(docroot, "dir2"),                          # 1
+            os.path.join(docroot, "dir2", "subdir1"),               # 2
+            os.path.join(docroot, "dir3"),                          # 3
+            os.path.join(docroot, "dir4"),                          # 4
+            os.path.join(docroot, "dir4", "subdir1"),               # 5
+            os.path.join(docroot, "dir4", "subdir1", "subsubdir1"), # 6
+            os.path.join(docroot, "dir4", "subdir2"),               # 7
+            os.path.join(docroot, "dir4", "subdir2", "dir1"),       # 8
+            os.path.join(docroot, "dir4", "subdir2", "dir2"),       # 9
+        )
+
+        for dir in dirnames:
+            os.mkdir(dir)
+
+        src = os.path.dirname(__file__)
+        filenames = [
+            os.path.join(src, f)
+            for f in os.listdir(src)
+            if os.path.isfile(os.path.join(src, f))
+        ]
+
+        for dirname in (docroot,) + dirnames[3:8+1]:
+            for filename in filenames[:5]:
+                copy(filename, dirname)
+        return docroot
+
+
+    def _getDocumentRoot(self):
+        if not hasattr(self, "_docroot"):
+            log.msg("Setting up docroot for %s" % (self.__class__,))
+
+            self._docroot = self.createDocumentRoot()
+
+        return self._docroot
+
+    def _setDocumentRoot(self, value):
+        self._docroot = value
+
+    docroot = property(_getDocumentRoot, _setDocumentRoot)
+
+    def _getSite(self):
+        if not hasattr(self, "_site"):
+            rootresource = self.resource_class(self.docroot)
+            rootresource.setAccessControlList(self.grantInherit(davxml.All()))
+            self._site = Site(rootresource)
+        return self._site
+
+    def _setSite(self, site):
+        self._site = site
+
+    site = property(_getSite, _setSite)
+
+    def setUp(self):
+        unittest.TestCase.setUp(self)
+        TestFile._cachedPropertyStores = {}
+
+    def tearDown(self):
+        unittest.TestCase.tearDown(self)
+        if hasattr(self, "_docroot"):
+            rmdir(self._docroot)
+
+    def mkdtemp(self, prefix):
+        """
+        Creates a new directory in the document root and returns its path and
+        URI.
+        """
+        path = mkdtemp(prefix=prefix + "_", dir=self.docroot)
+        uri  = joinURL("/", url_quote(os.path.basename(path))) + "/"
+
+        return (path, uri)
+
+    def send(self, request, callback):
+        log.msg("Sending %s request for URI %s" % (request.method, request.uri))
+
+        d = request.locateResource(request.uri)
+        d.addCallback(lambda resource: resource.renderHTTP(request))
+        d.addCallback(request._cbFinishRender)
+
+        if callback:
+            if type(callback) is tuple:
+                d.addCallbacks(*callback)
+            else:
+                d.addCallback(callback)
+
+        return d
+
+class Site:
+    # FIXME: There is no ISite interface; there should be.
+    # implements(ISite)
+
+    def __init__(self, resource):
+        self.resource = resource
+
+def dircmp(dir1, dir2):
+    dc = DirCompare(dir1, dir2)
+    return bool(
+        dc.left_only or dc.right_only or
+        dc.diff_files or
+        dc.common_funny or dc.funny_files
+    )
+
+def serialize(f, work):
+    d = Deferred()
+
+    def oops(error):
+        d.errback(error)
+
+    def do_serialize(_):
+        try:
+            args = work.next()
+        except StopIteration:
+            d.callback(None)
+        else:
+            r = f(*args)
+            r.addCallback(do_serialize)
+            r.addErrback(oops)
+
+    do_serialize(None)
+
+    return d

Copied: CalendarServer/trunk/twext/web2/dav/util.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/util.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/util.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/util.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,202 @@
+# -*- test-case-name: twext.web2.test.test_util -*-
+##
+# 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
+##
+
+"""
+Utilities
+
+This API is considered private to static.py and is therefore subject to
+change.
+"""
+
+__all__ = [
+    "allDataFromStream",
+    "davXMLFromStream",
+    "noDataFromStream",
+    "normalizeURL",
+    "joinURL",
+    "parentForURL",
+    "unimplemented",
+    "bindMethods",
+]
+
+import urllib
+from urlparse import urlsplit, urlunsplit
+import posixpath # Careful; this module is not documented as public API
+
+from twisted.python import log
+from twisted.python.failure import Failure
+from twisted.internet.defer import succeed
+from twext.web2.stream import readStream
+
+from twext.web2.dav import davxml
+
+##
+# Reading request body
+##
+
+def allDataFromStream(stream, filter=None):
+    data = []
+    def gotAllData(_):
+        if not data: return None
+        result = "".join([str(x) for x in data])
+        if filter is None:
+            return result
+        else:
+            return filter(result)
+    return readStream(stream, data.append).addCallback(gotAllData)
+
+def davXMLFromStream(stream):
+    # FIXME:
+    #   This reads the request body into a string and then parses it.
+    #   A better solution would parse directly and incrementally from the
+    #   request stream.
+    if stream is None:
+        return succeed(None)
+
+    def parse(xml):
+        try:
+            doc = davxml.WebDAVDocument.fromString(xml)
+            doc.root_element.validate()
+            return doc
+        except ValueError:
+            log.err("Bad XML:\n%s" % (xml,))
+            raise
+    return allDataFromStream(stream, parse)
+
+def noDataFromStream(stream):
+    def gotData(data):
+        if data: raise ValueError("Stream contains unexpected data.")
+    return readStream(stream, gotData)
+
+##
+# URLs
+##
+
+def normalizeURL(url):
+    """
+    Normalized a URL.
+    @param url: a URL.
+    @return: the normalized representation of C{url}.  The returned URL will
+        never contain a trailing C{"/"}; it is up to the caller to determine
+        whether the resource referred to by the URL is a collection and add a
+        trailing C{"/"} if so.
+    """
+    def cleanup(path):
+        # For some silly reason, posixpath.normpath doesn't clean up '//' at the
+        # start of a filename, so let's clean it up here.
+        if path[0] == "/":
+            count = 0
+            for char in path:
+                if char != "/": break
+                count += 1
+            path = path[count-1:]
+
+        return path
+
+    (scheme, host, path, query, fragment) = urlsplit(cleanup(url))
+
+    path = cleanup(posixpath.normpath(urllib.unquote(path)))
+
+    return urlunsplit((scheme, host, urllib.quote(path), query, fragment))
+
+def joinURL(*urls):
+    """
+    Appends URLs in series.
+    @param urls: URLs to join.
+    @return: the normalized URL formed by combining each URL in C{urls}.  The
+        returned URL will contain a trailing C{"/"} if and only if the last
+        given URL contains a trailing C{"/"}.
+    """
+    if len(urls) > 0 and len(urls[-1]) > 0 and urls[-1][-1] == "/":
+        trailing = "/"
+    else:
+        trailing = ""
+
+    url = normalizeURL("/".join([url for url in urls]))
+    if url == "/":
+        return "/"
+    else:
+        return url + trailing
+
+def parentForURL(url):
+    """
+    Extracts the URL of the containing collection resource for the resource
+    corresponding to a given URL.
+    @param url: an absolute (server-relative is OK) URL.
+    @return: the normalized URL of the collection resource containing the
+        resource corresponding to C{url}.  The returned URL will always contain
+        a trailing C{"/"}.
+    """
+    (scheme, host, path, query, fragment) = urlsplit(normalizeURL(url))
+
+    index = path.rfind("/")
+    if index is 0:
+        if path == "/":
+            return None
+        else:
+            path = "/"
+    else:
+        if index is -1:
+            raise ValueError("Invalid URL: %s" % (url,))
+        else:
+            path = path[:index] + "/"
+
+    return urlunsplit((scheme, host, path, query, fragment))
+
+##
+# Python magic
+##
+
+def unimplemented(obj):
+    """
+    Throw an exception signifying that the current method is unimplemented
+    and should not have been invoked.
+    """
+    import inspect
+    caller = inspect.getouterframes(inspect.currentframe())[1][3]
+    raise NotImplementedError("Method %s is unimplemented in subclass %s" % (caller, obj.__class__))
+
+def bindMethods(module, clazz, prefixes=("preconditions_", "http_", "report_")):
+    """
+    Binds all functions in the given module (as defined by that module's
+    C{__all__} attribute) which start with any of the given prefixes as methods
+    of the given class.
+    @param module: the module in which to search for functions.
+    @param clazz: the class to bind found functions to as methods.
+    @param prefixes: a sequence of prefixes to match found functions against.
+    """
+    for submodule_name in module.__all__:
+        try:
+            __import__(module.__name__ + "." + submodule_name)
+        except ImportError:
+            log.err("Unable to import module %s" % (module.__name__ + "." + submodule_name,))
+            Failure().raiseException()
+        submodule = getattr(module, submodule_name)
+        for method_name in submodule.__all__:
+            for prefix in prefixes:
+                if method_name.startswith(prefix):
+                    method = getattr(submodule, method_name)
+                    setattr(clazz, method_name, method)
+                    break

Copied: CalendarServer/trunk/twext/web2/dav/xattrprops.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dav/xattrprops.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dav/xattrprops.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dav/xattrprops.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,264 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+##
+# 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.
+#
+##
+
+"""
+DAV Property store using file system extended attributes.
+
+This API is considered private to static.py and is therefore subject to
+change.
+"""
+
+__all__ = ["xattrPropertyStore"]
+
+import urllib
+import sys
+import zlib
+import errno
+
+from operator import setitem
+from zlib import compress, decompress
+from cPickle import UnpicklingError, loads as unpickle
+
+import xattr
+
+if getattr(xattr, 'xattr', None) is None:
+    raise ImportError("wrong xattr package imported")
+
+from twisted.python.util import untilConcludes
+from twisted.python.failure import Failure
+from twisted.python.log import err
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2.dav import davxml
+from twext.web2.dav.http import statusForFailure
+
+# RFC 2518 Section 12.13.1 says that removal of non-existing property
+# is not an error.  python-xattr on Linux fails with ENODATA in this
+# case.  On OS X, the xattr library fails with ENOATTR, which CPython
+# does not expose.  Its value is 93.
+_ATTR_MISSING = (errno.ENODATA, 93)
+
+
+class xattrPropertyStore (object):
+    """
+
+    This implementation uses Bob Ippolito's xattr package, available from::
+
+        http://undefined.org/python/#xattr
+
+    Note that the Bob's xattr package is specific to Linux and Darwin, at least
+    presently.
+    """
+    #
+    # Dead properties are stored as extended attributes on disk.  In order to
+    # avoid conflicts with other attributes, prefix dead property names.
+    #
+    deadPropertyXattrPrefix = "WebDAV:"
+
+    # Linux seems to require that attribute names use a "user." prefix.
+    # FIXME: Is is a system-wide thing, or a per-filesystem thing?
+    #   If the latter, how to we detect the file system?
+    if sys.platform == "linux2":
+        deadPropertyXattrPrefix = "user."
+
+    def _encode(clazz, name):
+        result = urllib.quote("{%s}%s" % name, safe='{}:')
+        r = clazz.deadPropertyXattrPrefix + result
+        return r
+
+    def _decode(clazz, name):
+        name = urllib.unquote(name[len(clazz.deadPropertyXattrPrefix):])
+
+        index = name.find("}")
+
+        if (index is -1 or not len(name) > index or not name[0] == "{"):
+            raise ValueError("Invalid encoded name: %r" % (name,))
+
+        return (name[1:index], name[index+1:])
+
+    _encode = classmethod(_encode)
+    _decode = classmethod(_decode)
+
+    def __init__(self, resource):
+        self.resource = resource
+        self.attrs = xattr.xattr(self.resource.fp.path)
+
+
+    def get(self, qname):
+        """
+        Retrieve the value of a property stored as an extended attribute on the
+        wrapped path.
+
+        @param qname: The property to retrieve as a two-tuple of namespace URI
+            and local name.
+
+        @raise HTTPError: If there is no value associated with the given
+            property.
+
+        @return: A L{WebDAVDocument} representing the value associated with the
+            given property.
+        """
+        try:
+            data = self.attrs.get(self._encode(qname))
+        except KeyError:
+            raise HTTPError(StatusResponse(
+                    responsecode.NOT_FOUND,
+                    "No such property: {%s}%s" % qname))
+        except IOError, e:
+            if e.errno in _ATTR_MISSING:
+                raise HTTPError(StatusResponse(
+                        responsecode.NOT_FOUND,
+                        "No such property: {%s}%s" % qname))
+            else:
+                raise HTTPError(StatusResponse(
+                        statusForFailure(Failure()),
+                        "Unable to read property: {%s}%s" % qname))
+
+        #
+        # Unserialize XML data from 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
+
+        try:
+            data = decompress(data)
+        except zlib.error:
+            legacy = True
+
+        try:
+            doc = davxml.WebDAVDocument.fromString(data)
+        except ValueError:
+            try:
+                doc = unpickle(data)
+            except UnpicklingError:
+                format = "Invalid property value stored on server: {%s}%s %s"
+                msg = format % (qname[0], qname[1], data)
+                err(None, msg)
+                raise HTTPError(
+                    StatusResponse(responsecode.INTERNAL_SERVER_ERROR, msg))
+            else:
+                legacy = True
+
+        if legacy:
+            self.set(doc.root_element)
+
+        return doc.root_element
+
+
+    def set(self, property):
+        """
+        Store the given property as an extended attribute on the wrapped path.
+
+        @param property: A L{WebDAVElement} to store.
+        """
+        key = self._encode(property.qname())
+        value = compress(property.toxml())
+        untilConcludes(setitem, self.attrs, key, value)
+
+        # Update the resource because we've modified it
+        self.resource.fp.restat()
+
+
+    def delete(self, qname):
+        """
+        Remove the extended attribute from the wrapped path which stores the
+        property given by C{qname}.
+
+        @param qname: The property to delete as a two-tuple of namespace URI
+            and local name.
+        """
+        key = self._encode(qname)
+        try:
+            try:
+                self.attrs.remove(key)
+            except KeyError:
+                pass
+            except IOError, e:
+                if e.errno not in _ATTR_MISSING:
+                    raise
+        except:
+            raise HTTPError(
+                StatusResponse(
+                    statusForFailure(Failure()),
+                    "Unable to delete property: " + key))
+
+
+    def contains(self, qname):
+        """
+        Determine whether the property given by C{qname} is stored in an
+        extended attribute of the wrapped path.
+
+        @param qname: The property to look up as a two-tuple of namespace URI
+            and local name.
+
+        @return: C{True} if the property exists, C{False} otherwise.
+        """
+        key = self._encode(qname)
+        try:
+            self.attrs.get(key)
+        except KeyError:
+            return False
+        except IOError, e:
+            if e.errno in _ATTR_MISSING or e.errno == errno.ENOENT:
+                return False
+            raise HTTPError(
+                StatusResponse(
+                    statusForFailure(Failure()),
+                    "Unable to read property: " + key))
+        else:
+            return True
+
+
+    def list(self):
+        """
+        Enumerate the property names stored in extended attributes of the
+        wrapped path.
+
+        @return: A C{list} of property names as two-tuples of namespace URI and
+            local name.
+        """
+        prefix = self.deadPropertyXattrPrefix
+        try:
+            attrs = iter(self.attrs)
+        except IOError:
+            raise HTTPError(
+                StatusResponse(
+                    statusForFailure(Failure()),
+                    "Unable to list properties: " + self.resource.fp.path))
+        else:
+            return [
+                self._decode(name)
+                for name
+                in attrs
+                if name.startswith(prefix)]

Copied: CalendarServer/trunk/twext/web2/dirlist.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/dirlist.py)
===================================================================
--- CalendarServer/trunk/twext/web2/dirlist.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/dirlist.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,140 @@
+##
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.
+#
+##
+
+"""Directory listing."""
+
+# system imports
+import os
+import urllib
+import stat
+import time
+
+# twisted imports
+from twext.web2 import iweb, resource, http, http_headers
+
+def formatFileSize(size):
+    if size < 1024:
+        return '%i' % size
+    elif size < (1024**2):
+        return '%iK' % (size / 1024)
+    elif size < (1024**3):
+        return '%iM' % (size / (1024**2))
+    else:
+        return '%iG' % (size / (1024**3))
+
+class DirectoryLister(resource.Resource):
+    def __init__(self, pathname, dirs=None,
+                 contentTypes={},
+                 contentEncodings={},
+                 defaultType='text/html'):
+        self.contentTypes = contentTypes
+        self.contentEncodings = contentEncodings
+        self.defaultType = defaultType
+        # dirs allows usage of the File to specify what gets listed
+        self.dirs = dirs
+        self.path = pathname
+        resource.Resource.__init__(self)
+
+    def data_listing(self, request, data):
+        if self.dirs is None:
+            directory = os.listdir(self.path)
+            directory.sort()
+        else:
+            directory = self.dirs
+
+        files = []
+
+        for path in directory:
+            url = urllib.quote(path, '/')
+            fullpath = os.path.join(self.path, path)
+            try:
+                st = os.stat(fullpath)
+            except OSError:
+                continue
+            if stat.S_ISDIR(st.st_mode):
+                url = url + '/'
+                files.append({
+                    'link': url,
+                    'linktext': path + "/",
+                    'size': '',
+                    'type': '-',
+                    'lastmod': time.strftime("%Y-%b-%d %H:%M", time.localtime(st.st_mtime))
+                    })
+            else:
+                from twext.web2.static import getTypeAndEncoding
+                mimetype, encoding = getTypeAndEncoding(
+                    path,
+                    self.contentTypes, self.contentEncodings, self.defaultType)
+                
+                filesize = st.st_size
+                files.append({
+                    'link': url,
+                    'linktext': path,
+                    'size': formatFileSize(filesize),
+                    'type': mimetype,
+                    'lastmod': time.strftime("%Y-%b-%d %H:%M", time.localtime(st.st_mtime))
+                    })
+
+        return files
+
+    def __repr__(self):  
+        return '<DirectoryLister of %r>' % self.path
+        
+    __str__ = __repr__
+
+
+    def render(self, request):
+        title = "Directory listing for %s" % urllib.unquote(request.path)
+    
+        s= """<html><head><title>%s</title><style>
+          th, .even td, .odd td { padding-right: 0.5em; font-family: monospace}
+          .even-dir { background-color: #efe0ef }
+          .even { background-color: #eee }
+          .odd-dir {background-color: #f0d0ef }
+          .odd { background-color: #dedede }
+          .icon { text-align: center }
+          .listing {
+              margin-left: auto;
+              margin-right: auto;
+              width: 50%%;
+              padding: 0.1em;
+              }
+
+          body { border: 0; padding: 0; margin: 0; background-color: #efefef;}
+          h1 {padding: 0.1em; background-color: #777; color: white; border-bottom: thin white dashed;}
+</style></head><body><div class="directory-listing"><h1>%s</h1>""" % (title,title)
+        s+="<table>"
+        s+="<tr><th>Filename</th><th>Size</th><th>Last Modified</th><th>File Type</th></tr>"
+        even = False
+        for row in self.data_listing(request, None):
+            s+='<tr class="%s">' % (even and 'even' or 'odd',)
+            s+='<td><a href="%(link)s">%(linktext)s</a></td><td align="right">%(size)s</td><td>%(lastmod)s</td><td>%(type)s</td></tr>' % row
+            even = not even
+                
+        s+="</table></div></body></html>"
+        response = http.Response(200, {}, s)
+        response.headers.setHeader("content-type", http_headers.MimeType('text', 'html'))
+        return response
+
+__all__ = ['DirectoryLister']

Copied: CalendarServer/trunk/twext/web2/error.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/error.py)
===================================================================
--- CalendarServer/trunk/twext/web2/error.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/error.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,123 @@
+##
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.
+#
+##
+
+"""
+Default error output filter for twext.web2.
+"""
+
+from twext.web2 import stream, http_headers
+from twext.web2.responsecode import *
+
+# 300 - Should include entity with choices
+# 301 -
+# 304 - Must include Date, ETag, Content-Location, Expires, Cache-Control, Vary.
+# 
+# 401 - Must include WWW-Authenticate.
+# 405 - Must include Allow.
+# 406 - Should include entity describing allowable characteristics
+# 407 - Must include Proxy-Authenticate
+# 413 - May  include Retry-After
+# 416 - Should include Content-Range
+# 503 - Should include Retry-After
+
+ERROR_MESSAGES = {
+    # 300
+    # no MULTIPLE_CHOICES
+    MOVED_PERMANENTLY: 'The document has permanently moved <a href="%(location)s">here</a>.',
+    FOUND: 'The document has temporarily moved <a href="%(location)s">here</a>.',
+    SEE_OTHER: 'The results are available <a href="%(location)s">here</a>.',
+    # no NOT_MODIFIED
+    USE_PROXY: "Access to this resource must be through the proxy %(location)s.",
+    # 306 unused
+    TEMPORARY_REDIRECT: 'The document has temporarily moved <a href="%(location)s">here</a>.',
+
+    # 400
+    BAD_REQUEST: "Your browser sent an invalid request.",
+    UNAUTHORIZED: "You are not authorized to view the resource at %(uri)s. Perhaps you entered a wrong password, or perhaps your browser doesn't support authentication.",
+    PAYMENT_REQUIRED: "Payment Required (useful result code, this...).",
+    FORBIDDEN: "You don't have permission to access %(uri)s.",
+    NOT_FOUND: "The resource %(uri)s cannot be found.",
+    NOT_ALLOWED: "The requested method %(method)s is not supported by %(uri)s.",
+    NOT_ACCEPTABLE: "No representation of %(uri)s that is acceptable to your client could be found.",
+    PROXY_AUTH_REQUIRED: "You are not authorized to view the resource at %(uri)s. Perhaps you entered a wrong password, or perhaps your browser doesn't support authentication.",
+    REQUEST_TIMEOUT: "Server timed out waiting for your client to finish sending the HTTP request.",
+    CONFLICT: "Conflict (?)",
+    GONE: "The resource %(uri)s has been permanently removed.",
+    LENGTH_REQUIRED: "The resource %(uri)s requires a Content-Length header.",
+    PRECONDITION_FAILED: "A precondition evaluated to false.",
+    REQUEST_ENTITY_TOO_LARGE: "The provided request entity data is too longer than the maximum for the method %(method)s at %(uri)s.",
+    REQUEST_URI_TOO_LONG: "The request URL is longer than the maximum on this server.",
+    UNSUPPORTED_MEDIA_TYPE: "The provided request data has a format not understood by the resource at %(uri)s.",
+    REQUESTED_RANGE_NOT_SATISFIABLE: "None of the ranges given in the Range request header are satisfiable by the resource %(uri)s.",
+    EXPECTATION_FAILED: "The server does support one of the expectations given in the Expect header.",
+
+    # 500
+    INTERNAL_SERVER_ERROR: "An internal error occurred trying to process your request. Sorry.",
+    NOT_IMPLEMENTED: "Some functionality requested is not implemented on this server.",
+    BAD_GATEWAY: "An upstream server returned an invalid response.",
+    SERVICE_UNAVAILABLE: "This server cannot service your request becaues it is overloaded.",
+    GATEWAY_TIMEOUT: "An upstream server is not responding.",
+    HTTP_VERSION_NOT_SUPPORTED: "HTTP Version not supported.",
+    INSUFFICIENT_STORAGE_SPACE: "There is insufficient storage space available to perform that request.",
+    NOT_EXTENDED: "This server does not support the a mandatory extension requested."
+}
+
+# Is there a good place to keep this function?
+def _escape(original):
+    if original is None:
+        return None
+    return original.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;")
+
+def defaultErrorHandler(request, response):
+    if response.stream is not None:
+        # Already got an error message
+        return response
+    if response.code < 300:
+        # We only do error messages
+        return response
+    
+    message = ERROR_MESSAGES.get(response.code, None)
+    if message is None:
+        # No message specified for that code
+        return response
+    
+    message = message % {
+        'uri':_escape(request.uri),
+        'location':_escape(response.headers.getHeader('location')),
+        'method':_escape(request.method)
+        }
+
+    title = RESPONSES.get(response.code, "")
+    body = ("<html><head><title>%d %s</title></head>"
+            "<body><h1>%s</h1>%s</body></html>") % (
+        response.code, title, title, message)
+    
+    response.headers.setHeader("content-type", http_headers.MimeType('text', 'html', {'charset':'utf-8'}))
+    response.stream = stream.MemoryStream(body)
+    
+    return response
+defaultErrorHandler.handleErrors = True
+
+
+__all__ = ['defaultErrorHandler',]

Copied: CalendarServer/trunk/twext/web2/fileupload.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/fileupload.py)
===================================================================
--- CalendarServer/trunk/twext/web2/fileupload.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/fileupload.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,397 @@
+##
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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 __future__ import generators
+
+import re
+from zope.interface import implements
+import urllib
+import tempfile
+
+from twisted.internet import defer
+from twext.web2.stream import IStream, FileStream, BufferedStream, readStream
+from twext.web2.stream import generatorToStream, readAndDiscard
+from twext.web2 import http_headers
+from cStringIO import StringIO
+
+###################################
+#####  Multipart MIME Reader  #####
+###################################
+
+class MimeFormatError(Exception):
+    pass
+
+# parseContentDispositionFormData is absolutely horrible, but as
+# browsers don't seem to believe in sensible quoting rules, it's
+# really the only way to handle the header.  (Quotes can be in the
+# filename, unescaped)
+cd_regexp = re.compile(
+    ' *form-data; *name="([^"]*)"(?:; *filename="(.*)")?$',
+    re.IGNORECASE)
+
+def parseContentDispositionFormData(value):
+    match = cd_regexp.match(value)
+    if not match:
+        # Error parsing. 
+        raise ValueError("Unknown content-disposition format.")
+    name=match.group(1)
+    filename=match.group(2)
+    return name, filename
+
+
+#@defer.deferredGenerator
+def _readHeaders(stream):
+    """Read the MIME headers. Assumes we've just finished reading in the
+    boundary string."""
+
+    ctype = fieldname = filename = None
+    headers = []
+    
+    # Now read headers
+    while 1:
+        line = stream.readline(size=1024)
+        if isinstance(line, defer.Deferred):
+            line = defer.waitForDeferred(line)
+            yield line
+            line = line.getResult()
+        #print "GOT", line
+        if not line.endswith('\r\n'):
+            if line == "":
+                raise MimeFormatError("Unexpected end of stream.")
+            else:
+                raise MimeFormatError("Header line too long")
+
+        line = line[:-2] # strip \r\n
+        if line == "":
+            break # End of headers
+        
+        parts = line.split(':', 1)
+        if len(parts) != 2:
+            raise MimeFormatError("Header did not have a :")
+        name, value = parts
+        name = name.lower()
+        headers.append((name, value))
+        
+        if name == "content-type":
+            ctype = http_headers.parseContentType(http_headers.tokenize((value,), foldCase=False))
+        elif name == "content-disposition":
+            fieldname, filename = parseContentDispositionFormData(value)
+        
+    if ctype is None:
+        ctype == http_headers.MimeType('application', 'octet-stream')
+    if fieldname is None:
+        raise MimeFormatError('Content-disposition invalid or omitted.')
+
+    # End of headers, return (field name, content-type, filename)
+    yield fieldname, filename, ctype
+    return
+_readHeaders = defer.deferredGenerator(_readHeaders)
+
+
+class _BoundaryWatchingStream(object):
+    def __init__(self, stream, boundary):
+        self.stream = stream
+        self.boundary = boundary
+        self.data = ''
+        self.deferred = defer.Deferred()
+        
+    length = None # unknown
+    def read(self):
+        if self.stream is None:
+            if self.deferred is not None:
+                deferred = self.deferred
+                self.deferred = None
+                deferred.callback(None)
+            return None
+        newdata = self.stream.read()
+        if isinstance(newdata, defer.Deferred):
+            return newdata.addCallbacks(self._gotRead, self._gotError)
+        return self._gotRead(newdata)
+
+    def _gotRead(self, newdata):
+        if not newdata:
+            raise MimeFormatError("Unexpected EOF")
+        # BLECH, converting buffer back into string.
+        self.data += str(newdata)
+        data = self.data
+        boundary = self.boundary
+        off = data.find(boundary)
+        
+        if off == -1:
+            # No full boundary, check for the first character
+            off = data.rfind(boundary[0], max(0, len(data)-len(boundary)))
+            if off != -1:
+                # We could have a partial boundary, store it for next time
+                self.data = data[off:]
+                return data[:off]
+            else:
+                self.data = ''
+                return data
+        else:
+            self.stream.pushback(data[off+len(boundary):])
+            self.stream = None
+            return data[:off]
+
+    def _gotError(self, err):
+        # Propogate error back to MultipartMimeStream also
+        if self.deferred is not None:
+            deferred = self.deferred
+            self.deferred = None
+            deferred.errback(err)
+        return err
+    
+    def close(self):
+        # Assume error will be raised again and handled by MMS?
+        readAndDiscard(self).addErrback(lambda _: None)
+        
+class MultipartMimeStream(object):
+    implements(IStream)
+    def __init__(self, stream, boundary):
+        self.stream = BufferedStream(stream)
+        self.boundary = "--"+boundary
+        self.first = True
+        
+    def read(self):
+        """
+        Return a deferred which will fire with a tuple of:
+        (fieldname, filename, ctype, dataStream)
+        or None when all done.
+        
+        Format errors will be sent to the errback.
+        
+        Returns None when all done.
+
+        IMPORTANT: you *must* exhaust dataStream returned by this call
+        before calling .read() again!
+        """
+        if self.first:
+            self.first = False
+            d = self._readFirstBoundary()
+        else:
+            d = self._readBoundaryLine()
+        d.addCallback(self._doReadHeaders)
+        d.addCallback(self._gotHeaders)
+        return d
+
+    def _readFirstBoundary(self):
+        #print "_readFirstBoundary"
+        line = self.stream.readline(size=1024)
+        if isinstance(line, defer.Deferred):
+            line = defer.waitForDeferred(line)
+            yield line
+            line = line.getResult()
+        if line != self.boundary + '\r\n':
+            raise MimeFormatError("Extra data before first boundary: %r looking for: %r" % (line, self.boundary + '\r\n'))
+        
+        self.boundary = "\r\n"+self.boundary
+        yield True
+        return
+    _readFirstBoundary = defer.deferredGenerator(_readFirstBoundary)
+
+    def _readBoundaryLine(self):
+        #print "_readBoundaryLine"
+        line = self.stream.readline(size=1024)
+        if isinstance(line, defer.Deferred):
+            line = defer.waitForDeferred(line)
+            yield line
+            line = line.getResult()
+        
+        if line == "--\r\n":
+            # THE END!
+            yield False
+            return
+        elif line != "\r\n":
+            raise MimeFormatError("Unexpected data on same line as boundary: %r" % (line,))
+        yield True
+        return
+    _readBoundaryLine = defer.deferredGenerator(_readBoundaryLine)
+
+    def _doReadHeaders(self, morefields):
+        #print "_doReadHeaders", morefields
+        if not morefields:
+            return None
+        return _readHeaders(self.stream)
+    
+    def _gotHeaders(self, headers):
+        if headers is None:
+            return None
+        bws = _BoundaryWatchingStream(self.stream, self.boundary)
+        self.deferred = bws.deferred
+        ret=list(headers)
+        ret.append(bws)
+        return tuple(ret)
+
+
+def readIntoFile(stream, outFile, maxlen):
+    """Read the stream into a file, but not if it's longer than maxlen.
+    Returns Deferred which will be triggered on finish.
+    """
+    curlen = [0]
+    def done(_):
+        return _
+    def write(data):
+        curlen[0] += len(data)
+        if curlen[0] > maxlen:
+            raise MimeFormatError("Maximum length of %d bytes exceeded." %
+                                  maxlen)
+        
+        outFile.write(data)
+    return readStream(stream, write).addBoth(done)
+
+#@defer.deferredGenerator
+def parseMultipartFormData(stream, boundary,
+                           maxMem=100*1024, maxFields=1024, maxSize=10*1024*1024):
+    # If the stream length is known to be too large upfront, abort immediately
+    
+    if stream.length is not None and stream.length > maxSize:
+        raise MimeFormatError("Maximum length of %d bytes exceeded." %
+                                  maxSize)
+    
+    mms = MultipartMimeStream(stream, boundary)
+    numFields = 0
+    args = {}
+    files = {}
+    
+    while 1:
+        datas = mms.read()
+        if isinstance(datas, defer.Deferred):
+            datas = defer.waitForDeferred(datas)
+            yield datas
+            datas = datas.getResult()
+        if datas is None:
+            break
+        
+        numFields+=1
+        if numFields == maxFields:
+            raise MimeFormatError("Maximum number of fields %d exceeded"%maxFields)
+        
+        # Parse data
+        fieldname, filename, ctype, stream = datas
+        if filename is None:
+            # Not a file
+            outfile = StringIO()
+            maxBuf = min(maxSize, maxMem)
+        else:
+            outfile = tempfile.NamedTemporaryFile()
+            maxBuf = maxSize
+        x = readIntoFile(stream, outfile, maxBuf)
+        if isinstance(x, defer.Deferred):
+            x = defer.waitForDeferred(x)
+            yield x
+            x = x.getResult()
+        if filename is None:
+            # Is a normal form field
+            outfile.seek(0)
+            data = outfile.read()
+            args.setdefault(fieldname, []).append(data)
+            maxMem -= len(data)
+            maxSize -= len(data)
+        else:
+            # Is a file upload
+            maxSize -= outfile.tell()
+            outfile.seek(0)
+            files.setdefault(fieldname, []).append((filename, ctype, outfile))
+        
+        
+    yield args, files
+    return
+parseMultipartFormData = defer.deferredGenerator(parseMultipartFormData)
+
+###################################
+##### x-www-urlencoded reader #####
+###################################
+
+
+def parse_urlencoded_stream(input, maxMem=100*1024,
+                     keep_blank_values=False, strict_parsing=False):
+    lastdata = ''
+    still_going=1
+    
+    while still_going:
+        try:
+            yield input.wait
+            data = input.next()
+        except StopIteration:
+            pairs = [lastdata]
+            still_going=0
+        else:
+            maxMem -= len(data)
+            if maxMem < 0:
+                raise MimeFormatError("Maximum length of %d bytes exceeded." %
+                                      maxMem)
+            pairs = str(data).split('&')
+            pairs[0] = lastdata + pairs[0]
+            lastdata=pairs.pop()
+        
+        for name_value in pairs:
+            nv = name_value.split('=', 1)
+            if len(nv) != 2:
+                if strict_parsing:
+                    raise MimeFormatError("bad query field: %s") % `name_value`
+                continue
+            if len(nv[1]) or keep_blank_values:
+                name = urllib.unquote(nv[0].replace('+', ' '))
+                value = urllib.unquote(nv[1].replace('+', ' '))
+                yield name, value
+parse_urlencoded_stream = generatorToStream(parse_urlencoded_stream)
+
+def parse_urlencoded(stream, maxMem=100*1024, maxFields=1024,
+                     keep_blank_values=False, strict_parsing=False):
+    d = {}
+    numFields = 0
+
+    s=parse_urlencoded_stream(stream, maxMem, keep_blank_values, strict_parsing)
+    
+    while 1:
+        datas = s.read()
+        if isinstance(datas, defer.Deferred):
+            datas = defer.waitForDeferred(datas)
+            yield datas
+            datas = datas.getResult()
+        if datas is None:
+            break
+        name, value = datas
+        
+        numFields += 1
+        if numFields == maxFields:
+            raise MimeFormatError("Maximum number of fields %d exceeded"%maxFields)
+        
+        if name in d:
+            d[name].append(value)
+        else:
+            d[name] = [value]
+    yield d
+    return
+parse_urlencoded = defer.deferredGenerator(parse_urlencoded)
+
+
+if __name__ == '__main__':
+    d = parseMultipartFormData(
+        FileStream(open("upload.txt")), "----------0xKhTmLbOuNdArY")
+    from twisted.python import log
+    d.addErrback(log.err)
+    def pr(s):
+        print s
+    d.addCallback(pr)
+
+__all__ = ['parseMultipartFormData', 'parse_urlencoded', 'parse_urlencoded_stream', 'MultipartMimeStream', 'MimeFormatError']

Deleted: CalendarServer/trunk/twext/web2/filter/__init__.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/filter/__init__.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/filter/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,7 +0,0 @@
-# -*- test-case-name: twext.web2.test.test_cgi -*-
-# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Output filters.
-"""

Copied: CalendarServer/trunk/twext/web2/filter/__init__.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/filter/__init__.py)
===================================================================
--- CalendarServer/trunk/twext/web2/filter/__init__.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/filter/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,7 @@
+# -*- test-case-name: twext.web2.test.test_cgi -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Output filters.
+"""

Deleted: CalendarServer/trunk/twext/web2/filter/gzip.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/filter/gzip.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/filter/gzip.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,79 +0,0 @@
-from __future__ import generators
-import struct
-import zlib
-from twext.web2 import stream
-
-# TODO: ungzip (can any browsers actually generate gzipped
-# upload data?) But it's necessary for client anyways.
-
-def gzipStream(input, compressLevel=6):
-    crc, size = zlib.crc32(''), 0
-    # magic header, compression method, no flags
-    header = '\037\213\010\000'
-    # timestamp
-    header += struct.pack('<L', 0)
-    # uh.. stuff
-    header += '\002\377'
-    yield header
-    
-    compress = zlib.compressobj(compressLevel, zlib.DEFLATED, -zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, 0)
-    _compress = compress.compress
-    _crc32 = zlib.crc32
-    
-    yield input.wait
-    for buf in input:
-        if len(buf) != 0:
-            crc = _crc32(buf, crc)
-            size += len(buf)
-            yield _compress(buf)
-        yield input.wait
-    
-    yield compress.flush()
-    yield struct.pack('<LL', crc & 0xFFFFFFFFL, size & 0xFFFFFFFFL)
-gzipStream=stream.generatorToStream(gzipStream)
-
-def deflateStream(input, compressLevel=6):
-    # NOTE: this produces RFC-conformant but some-browser-incompatible output.
-    # The RFC says that you're supposed to output zlib-format data, but many
-    # browsers expect raw deflate output. Luckily all those browsers support
-    # gzip, also, so they won't even see deflate output. 
-    compress = zlib.compressobj(compressLevel, zlib.DEFLATED, zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, 0)
-    _compress = compress.compress
-    yield input.wait
-    for buf in input:
-        if len(buf) != 0:
-            yield _compress(buf)
-        yield input.wait
-
-    yield compress.flush()
-deflateStream=stream.generatorToStream(deflateStream)
-
-def gzipfilter(request, response):
-    if response.stream is None or response.headers.getHeader('content-encoding'):
-        # Empty stream, or already compressed.
-        return response
-    
-    # FIXME: make this a more flexible matching scheme
-    mimetype = response.headers.getHeader('content-type')
-    if not mimetype or mimetype.mediaType != 'text':
-        return response
-    
-    # Make sure to note we're going to return different content depending on
-    # the accept-encoding header.
-    vary = response.headers.getHeader('vary', [])
-    if 'accept-encoding' not in vary:
-        response.headers.setHeader('vary', vary+['accept-encoding'])
-    
-    ae = request.headers.getHeader('accept-encoding', {})
-    compressor = None
-    # Always prefer gzip over deflate no matter what their q-values are.
-    if ae.get('gzip', 0):
-        response.stream = gzipStream(response.stream)
-        response.headers.setHeader('content-encoding', ['gzip'])
-    elif ae.get('deflate', 0):
-        response.stream = deflateStream(response.stream)
-        response.headers.setHeader('content-encoding', ['deflate'])
-    
-    return response
-
-__all__ = ['gzipfilter']

Copied: CalendarServer/trunk/twext/web2/filter/gzip.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/filter/gzip.py)
===================================================================
--- CalendarServer/trunk/twext/web2/filter/gzip.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/filter/gzip.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,79 @@
+from __future__ import generators
+import struct
+import zlib
+from twext.web2 import stream
+
+# TODO: ungzip (can any browsers actually generate gzipped
+# upload data?) But it's necessary for client anyways.
+
+def gzipStream(input, compressLevel=6):
+    crc, size = zlib.crc32(''), 0
+    # magic header, compression method, no flags
+    header = '\037\213\010\000'
+    # timestamp
+    header += struct.pack('<L', 0)
+    # uh.. stuff
+    header += '\002\377'
+    yield header
+    
+    compress = zlib.compressobj(compressLevel, zlib.DEFLATED, -zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, 0)
+    _compress = compress.compress
+    _crc32 = zlib.crc32
+    
+    yield input.wait
+    for buf in input:
+        if len(buf) != 0:
+            crc = _crc32(buf, crc)
+            size += len(buf)
+            yield _compress(buf)
+        yield input.wait
+    
+    yield compress.flush()
+    yield struct.pack('<LL', crc & 0xFFFFFFFFL, size & 0xFFFFFFFFL)
+gzipStream=stream.generatorToStream(gzipStream)
+
+def deflateStream(input, compressLevel=6):
+    # NOTE: this produces RFC-conformant but some-browser-incompatible output.
+    # The RFC says that you're supposed to output zlib-format data, but many
+    # browsers expect raw deflate output. Luckily all those browsers support
+    # gzip, also, so they won't even see deflate output. 
+    compress = zlib.compressobj(compressLevel, zlib.DEFLATED, zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, 0)
+    _compress = compress.compress
+    yield input.wait
+    for buf in input:
+        if len(buf) != 0:
+            yield _compress(buf)
+        yield input.wait
+
+    yield compress.flush()
+deflateStream=stream.generatorToStream(deflateStream)
+
+def gzipfilter(request, response):
+    if response.stream is None or response.headers.getHeader('content-encoding'):
+        # Empty stream, or already compressed.
+        return response
+    
+    # FIXME: make this a more flexible matching scheme
+    mimetype = response.headers.getHeader('content-type')
+    if not mimetype or mimetype.mediaType != 'text':
+        return response
+    
+    # Make sure to note we're going to return different content depending on
+    # the accept-encoding header.
+    vary = response.headers.getHeader('vary', [])
+    if 'accept-encoding' not in vary:
+        response.headers.setHeader('vary', vary+['accept-encoding'])
+    
+    ae = request.headers.getHeader('accept-encoding', {})
+    compressor = None
+    # Always prefer gzip over deflate no matter what their q-values are.
+    if ae.get('gzip', 0):
+        response.stream = gzipStream(response.stream)
+        response.headers.setHeader('content-encoding', ['gzip'])
+    elif ae.get('deflate', 0):
+        response.stream = deflateStream(response.stream)
+        response.headers.setHeader('content-encoding', ['deflate'])
+    
+    return response
+
+__all__ = ['gzipfilter']

Deleted: CalendarServer/trunk/twext/web2/filter/location.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/filter/location.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/filter/location.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,30 +0,0 @@
-from twext.web2 import responsecode
-import urlparse
-
-__all__ = ['addLocation']
-
-def addLocation(request, location):
-    """
-    Add a C{location} header to the response if the response status is
-    CREATED.
-    @param request: L{IRequest} the request being processed
-    @param location: the URI to use in the C{location} header
-    """
-    def locationFilter(request, response):
-        if (response.code == responsecode.CREATED):
-            #
-            # Check to see whether we have an absolute URI or not.
-            # If not, have the request turn it into an absolute URI.
-            #
-            (scheme, host, path, params, querystring, fragment) = urlparse.urlparse(location)
-
-            if scheme == "":
-                uri = request.unparseURL(path=location)
-            else:
-                uri = location
-        
-            response.headers.setHeader("location", uri)
-
-        return response
-
-    request.addResponseFilter(locationFilter)

Copied: CalendarServer/trunk/twext/web2/filter/location.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/filter/location.py)
===================================================================
--- CalendarServer/trunk/twext/web2/filter/location.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/filter/location.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,30 @@
+from twext.web2 import responsecode
+import urlparse
+
+__all__ = ['addLocation']
+
+def addLocation(request, location):
+    """
+    Add a C{location} header to the response if the response status is
+    CREATED.
+    @param request: L{IRequest} the request being processed
+    @param location: the URI to use in the C{location} header
+    """
+    def locationFilter(request, response):
+        if (response.code == responsecode.CREATED):
+            #
+            # Check to see whether we have an absolute URI or not.
+            # If not, have the request turn it into an absolute URI.
+            #
+            (scheme, host, path, params, querystring, fragment) = urlparse.urlparse(location)
+
+            if scheme == "":
+                uri = request.unparseURL(path=location)
+            else:
+                uri = location
+        
+            response.headers.setHeader("location", uri)
+
+        return response
+
+    request.addResponseFilter(locationFilter)

Deleted: CalendarServer/trunk/twext/web2/filter/range.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/filter/range.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/filter/range.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,128 +0,0 @@
-# -*- test-case-name: twext.web2.test.test_stream -*-
-
-import time, os
-
-from twext.web2 import http, http_headers, responsecode, stream
-
-# Some starts at writing a response filter to handle request ranges.
-
-class UnsatisfiableRangeRequest(Exception):
-    pass
-
-def canonicalizeRange((start, end), size):
-    """Return canonicalized (start, end) or raises UnsatisfiableRangeRequest
-    exception.
-
-    NOTE: end is the last byte *inclusive*, which is not the usual convention
-    in python! Be very careful! A range of 0,1 should return 2 bytes."""
-    
-    # handle "-500" ranges
-    if start is None:
-        start = max(0, size-end)
-        end = None
-    
-    if end is None or end >= size:
-        end = size - 1
-        
-    if start >= size:
-        raise UnsatisfiableRangeRequest
-    
-    return start,end
-
-def makeUnsatisfiable(request, oldresponse):
-    if request.headers.hasHeader('if-range'):
-        return oldresponse # Return resource instead of error
-    response = http.Response(responsecode.REQUESTED_RANGE_NOT_SATISFIABLE)
-    response.headers.setHeader("content-range", ('bytes', None, None, oldresponse.stream.length))
-    return response
-
-def makeSegment(inputStream, lastOffset, start, end):
-    offset = start - lastOffset
-    length = end + 1 - start
-    
-    if offset != 0:
-        before, inputStream = inputStream.split(offset)
-        before.close()
-    return inputStream.split(length)
-
-def rangefilter(request, oldresponse):
-    if oldresponse.stream is None:
-        return oldresponse
-    size = oldresponse.stream.length
-    if size is None:
-        # Does not deal with indeterminate length outputs
-        return oldresponse
-
-    oldresponse.headers.setHeader('accept-ranges',('bytes',))
-    
-    rangespec = request.headers.getHeader('range')
-    
-    # If we've got a range header and the If-Range header check passes, and
-    # the range type is bytes, do a partial response.
-    if (rangespec is not None and http.checkIfRange(request, oldresponse) and
-        rangespec[0] == 'bytes'):
-        # If it's a single range, return a simple response
-        if len(rangespec[1]) == 1:
-            try:
-                start,end = canonicalizeRange(rangespec[1][0], size)
-            except UnsatisfiableRangeRequest:
-                return makeUnsatisfiable(request, oldresponse)
-
-            response = http.Response(responsecode.PARTIAL_CONTENT, oldresponse.headers)
-            response.headers.setHeader('content-range',('bytes',start, end, size))
-            
-            content, after = makeSegment(oldresponse.stream, 0, start, end)
-            after.close()
-            response.stream = content
-            return response
-        else:
-            # Return a multipart/byteranges response
-            lastOffset = -1
-            offsetList = []
-            for arange in rangespec[1]:
-                try:
-                    start,end = canonicalizeRange(arange, size)
-                except UnsatisfiableRangeRequest:
-                    continue
-                if start <= lastOffset:
-                    # Stupid client asking for out-of-order or overlapping ranges, PUNT!
-                    return oldresponse
-                offsetList.append((start,end))
-                lastOffset = end
-
-            if not offsetList:
-                return makeUnsatisfiable(request, oldresponse)
-            
-            content_type = oldresponse.headers.getRawHeaders('content-type')
-            boundary = "%x%x" % (int(time.time()*1000000), os.getpid())
-            response = http.Response(responsecode.PARTIAL_CONTENT, oldresponse.headers)
-            
-            response.headers.setHeader('content-type',
-                http_headers.MimeType('multipart', 'byteranges',
-                                      [('boundary', boundary)]))
-            response.stream = out = stream.CompoundStream()
-            
-            
-            lastOffset = 0
-            origStream = oldresponse.stream
-
-            headerString = "\r\n--%s" % boundary
-            if len(content_type) == 1:
-                headerString+='\r\nContent-Type: %s' % content_type[0]
-            headerString+="\r\nContent-Range: %s\r\n\r\n"
-            
-            for start,end in offsetList:
-                out.addStream(headerString % 
-                    http_headers.generateContentRange(('bytes', start, end, size)))
-
-                content, origStream = makeSegment(origStream, lastOffset, start, end)
-                lastOffset = end + 1
-                out.addStream(content)
-            origStream.close()
-            out.addStream("\r\n--%s--\r\n" % boundary)
-            return response
-    else:
-        return oldresponse
-
-    
-__all__ = ['rangefilter']

Copied: CalendarServer/trunk/twext/web2/filter/range.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/filter/range.py)
===================================================================
--- CalendarServer/trunk/twext/web2/filter/range.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/filter/range.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,128 @@
+# -*- test-case-name: twext.web2.test.test_stream -*-
+
+import time, os
+
+from twext.web2 import http, http_headers, responsecode, stream
+
+# Some starts at writing a response filter to handle request ranges.
+
+class UnsatisfiableRangeRequest(Exception):
+    pass
+
+def canonicalizeRange((start, end), size):
+    """Return canonicalized (start, end) or raises UnsatisfiableRangeRequest
+    exception.
+
+    NOTE: end is the last byte *inclusive*, which is not the usual convention
+    in python! Be very careful! A range of 0,1 should return 2 bytes."""
+    
+    # handle "-500" ranges
+    if start is None:
+        start = max(0, size-end)
+        end = None
+    
+    if end is None or end >= size:
+        end = size - 1
+        
+    if start >= size:
+        raise UnsatisfiableRangeRequest
+    
+    return start,end
+
+def makeUnsatisfiable(request, oldresponse):
+    if request.headers.hasHeader('if-range'):
+        return oldresponse # Return resource instead of error
+    response = http.Response(responsecode.REQUESTED_RANGE_NOT_SATISFIABLE)
+    response.headers.setHeader("content-range", ('bytes', None, None, oldresponse.stream.length))
+    return response
+
+def makeSegment(inputStream, lastOffset, start, end):
+    offset = start - lastOffset
+    length = end + 1 - start
+    
+    if offset != 0:
+        before, inputStream = inputStream.split(offset)
+        before.close()
+    return inputStream.split(length)
+
+def rangefilter(request, oldresponse):
+    if oldresponse.stream is None:
+        return oldresponse
+    size = oldresponse.stream.length
+    if size is None:
+        # Does not deal with indeterminate length outputs
+        return oldresponse
+
+    oldresponse.headers.setHeader('accept-ranges',('bytes',))
+    
+    rangespec = request.headers.getHeader('range')
+    
+    # If we've got a range header and the If-Range header check passes, and
+    # the range type is bytes, do a partial response.
+    if (rangespec is not None and http.checkIfRange(request, oldresponse) and
+        rangespec[0] == 'bytes'):
+        # If it's a single range, return a simple response
+        if len(rangespec[1]) == 1:
+            try:
+                start,end = canonicalizeRange(rangespec[1][0], size)
+            except UnsatisfiableRangeRequest:
+                return makeUnsatisfiable(request, oldresponse)
+
+            response = http.Response(responsecode.PARTIAL_CONTENT, oldresponse.headers)
+            response.headers.setHeader('content-range',('bytes',start, end, size))
+            
+            content, after = makeSegment(oldresponse.stream, 0, start, end)
+            after.close()
+            response.stream = content
+            return response
+        else:
+            # Return a multipart/byteranges response
+            lastOffset = -1
+            offsetList = []
+            for arange in rangespec[1]:
+                try:
+                    start,end = canonicalizeRange(arange, size)
+                except UnsatisfiableRangeRequest:
+                    continue
+                if start <= lastOffset:
+                    # Stupid client asking for out-of-order or overlapping ranges, PUNT!
+                    return oldresponse
+                offsetList.append((start,end))
+                lastOffset = end
+
+            if not offsetList:
+                return makeUnsatisfiable(request, oldresponse)
+            
+            content_type = oldresponse.headers.getRawHeaders('content-type')
+            boundary = "%x%x" % (int(time.time()*1000000), os.getpid())
+            response = http.Response(responsecode.PARTIAL_CONTENT, oldresponse.headers)
+            
+            response.headers.setHeader('content-type',
+                http_headers.MimeType('multipart', 'byteranges',
+                                      [('boundary', boundary)]))
+            response.stream = out = stream.CompoundStream()
+            
+            
+            lastOffset = 0
+            origStream = oldresponse.stream
+
+            headerString = "\r\n--%s" % boundary
+            if len(content_type) == 1:
+                headerString+='\r\nContent-Type: %s' % content_type[0]
+            headerString+="\r\nContent-Range: %s\r\n\r\n"
+            
+            for start,end in offsetList:
+                out.addStream(headerString % 
+                    http_headers.generateContentRange(('bytes', start, end, size)))
+
+                content, origStream = makeSegment(origStream, lastOffset, start, end)
+                lastOffset = end + 1
+                out.addStream(content)
+            origStream.close()
+            out.addStream("\r\n--%s--\r\n" % boundary)
+            return response
+    else:
+        return oldresponse
+
+    
+__all__ = ['rangefilter']

Modified: CalendarServer/trunk/twext/web2/http.py
===================================================================
--- CalendarServer/trunk/twext/web2/http.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/http.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,31 +1,494 @@
+# -*- test-case-name: twext.web2.test.test_http -*-
 ##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# Copyright (c) 2010 Apple Computer, 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
+# 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:
 #
-# http://www.apache.org/licenses/LICENSE-2.0
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
 #
-# 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.
+# 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.
+#
 ##
 
+"""HyperText Transfer Protocol implementation.
+
+The second coming.
+
+Maintainer: James Y Knight
+
 """
-Extensions to twisted.web2.http
-"""
+#        import traceback; log.msg(''.join(traceback.format_stack()))
 
-__all__ = [
-    "XMLResponse",
-]
+# system imports
+import time
+import cgi
 
-from twisted.web2.http import Response
-from twisted.web2.http_headers import MimeType
+# twisted imports
+from twisted.internet import interfaces, error
+from twisted.python import log, components
+from zope.interface import implements
 
+# sibling imports
+from twext.web2 import responsecode
+from twext.web2 import http_headers
+from twext.web2 import iweb
+from twext.web2 import stream
+from twext.web2.stream import IByteStream, readAndDiscard
 
+defaultPortForScheme = {'http': 80, 'https':443, 'ftp':21}
+
+def splitHostPort(scheme, hostport):
+    """Split the host in "host:port" format into host and port fields. 
+    If port was not specified, use the default for the given scheme, if
+    known. Returns a tuple of (hostname, portnumber)."""
+    
+    # Split hostport into host and port
+    hostport = hostport.split(':', 1)
+    try:
+        if len(hostport) == 2:
+            return hostport[0], int(hostport[1])
+    except ValueError:
+        pass
+    return hostport[0], defaultPortForScheme.get(scheme, 0)
+
+
+def parseVersion(strversion):
+    """Parse version strings of the form Protocol '/' Major '.' Minor. E.g. 'HTTP/1.1'.
+    Returns (protocol, major, minor).
+    Will raise ValueError on bad syntax."""
+
+    proto, strversion = strversion.split('/')
+    major, minor = strversion.split('.')
+    major, minor = int(major), int(minor)
+    if major < 0 or minor < 0:
+        raise ValueError("negative number")
+    return (proto.lower(), major, minor)
+
+
+class HTTPError(Exception):
+    def __init__(self, codeOrResponse):
+        """An Exception for propagating HTTP Error Responses.
+
+        @param codeOrResponse: The numeric HTTP code or a complete http.Response
+            object.
+        @type codeOrResponse: C{int} or L{http.Response}
+        """
+        self.response = iweb.IResponse(codeOrResponse)
+        Exception.__init__(self, str(self.response))
+
+    def __repr__(self):
+        return "<%s %s>" % (self.__class__.__name__, self.response)
+
+
+class Response(object):
+    """An object representing an HTTP Response to be sent to the client.
+    """
+    implements(iweb.IResponse)
+    
+    code = responsecode.OK
+    headers = None
+    stream = None
+    
+    def __init__(self, code=None, headers=None, stream=None):
+        """
+        @param code: The HTTP status code for this Response
+        @type code: C{int}
+        
+        @param headers: Headers to be sent to the client.
+        @type headers: C{dict}, L{twext.web2.http_headers.Headers}, or 
+            C{None}
+        
+        @param stream: Content body to send to the HTTP client
+        @type stream: L{twext.web2.stream.IByteStream}
+        """
+
+        if code is not None:
+            self.code = int(code)
+
+        if headers is not None:
+            if isinstance(headers, dict):
+                headers = http_headers.Headers(headers)
+            self.headers=headers
+        else:
+            self.headers = http_headers.Headers()
+
+        if stream is not None:
+            self.stream = IByteStream(stream)
+
+    def __repr__(self):
+        if self.stream is None:
+            streamlen = None
+        else:
+            streamlen = self.stream.length
+
+        return "<%s.%s code=%d, streamlen=%s>" % (self.__module__, self.__class__.__name__, self.code, streamlen)
+
+
+class StatusResponse (Response):
+    """
+    A L{Response} object which simply contains a status code and a description of
+    what happened.
+    """
+    def __init__(self, code, description, title=None):
+        """
+        @param code: a response code in L{responsecode.RESPONSES}.
+        @param description: a string description.
+        @param title: the message title.  If not specified or C{None}, defaults
+            to C{responsecode.RESPONSES[code]}.
+        """
+        if title is None:
+            title = cgi.escape(responsecode.RESPONSES[code])
+
+        output = "".join((
+            "<html>",
+            "<head>",
+            "<title>%s</title>" % (title,),
+            "</head>",
+            "<body>",
+            "<h1>%s</h1>" % (title,),
+            "<p>%s</p>" % (cgi.escape(description),),
+            "</body>",
+            "</html>",
+        ))
+
+        if type(output) == unicode:
+            output = output.encode("utf-8")
+            mime_params = {"charset": "utf-8"}
+        else:
+            mime_params = {}
+
+        super(StatusResponse, self).__init__(code=code, stream=output)
+
+        self.headers.setHeader("content-type", http_headers.MimeType("text", "html", mime_params))
+
+        self.description = description
+
+    def __repr__(self):
+        return "<%s %s %s>" % (self.__class__.__name__, self.code, self.description)
+
+
+class RedirectResponse (StatusResponse):
+    """
+    A L{Response} object that contains a redirect to another network location.
+    """
+    def __init__(self, location):
+        """
+        @param location: the URI to redirect to.
+        """
+        super(RedirectResponse, self).__init__(
+            responsecode.MOVED_PERMANENTLY,
+            "Document moved to %s." % (location,)
+        )
+
+        self.headers.setHeader("location", location)
+
+        
+def NotModifiedResponse(oldResponse=None):
+    if oldResponse is not None:
+        headers=http_headers.Headers()
+        for header in (
+            # Required from sec 10.3.5:
+            'date', 'etag', 'content-location', 'expires',
+            'cache-control', 'vary',
+            # Others:
+            'server', 'proxy-authenticate', 'www-authenticate', 'warning'):
+            value = oldResponse.headers.getRawHeaders(header)
+            if value is not None:
+                headers.setRawHeaders(header, value)
+    else:
+        headers = None
+    return Response(code=responsecode.NOT_MODIFIED, headers=headers)
+    
+
+def checkPreconditions(request, response=None, entityExists=True, etag=None, lastModified=None):
+    """Check to see if this request passes the conditional checks specified
+    by the client. May raise an HTTPError with result codes L{NOT_MODIFIED}
+    or L{PRECONDITION_FAILED}, as appropriate.
+
+    This function is called automatically as an output filter for GET and
+    HEAD requests. With GET/HEAD, it is not important for the precondition
+    check to occur before doing the action, as the method is non-destructive.
+
+    However, if you are implementing other request methods, like PUT
+    for your resource, you will need to call this after determining
+    the etag and last-modified time of the existing resource but
+    before actually doing the requested action. In that case, 
+
+    This examines the appropriate request headers for conditionals,
+    (If-Modified-Since, If-Unmodified-Since, If-Match, If-None-Match,
+    or If-Range), compares with the etag and last and
+    and then sets the response code as necessary.
+
+    @param response: This should be provided for GET/HEAD methods. If
+             it is specified, the etag and lastModified arguments will
+             be retrieved automatically from the response headers and
+             shouldn't be separately specified. Not providing the
+             response with a GET request may cause the emitted
+             "Not Modified" responses to be non-conformant.
+             
+    @param entityExists: Set to False if the entity in question doesn't
+             yet exist. Necessary for PUT support with 'If-None-Match: *'.
+             
+    @param etag: The etag of the resource to check against, or None.
+    
+    @param lastModified: The last modified date of the resource to check
+              against, or None.
+              
+    @raise: HTTPError: Raised when the preconditions fail, in order to
+             abort processing and emit an error page.
+
+    """
+    if response:
+        assert etag is None and lastModified is None
+        # if the code is some sort of error code, don't do anything
+        if not ((response.code >= 200 and response.code <= 299)
+                or response.code == responsecode.PRECONDITION_FAILED):
+            return False
+        etag = response.headers.getHeader("etag")
+        lastModified = response.headers.getHeader("last-modified")
+    
+    def matchETag(tags, allowWeak):
+        if entityExists and '*' in tags:
+            return True
+        if etag is None:
+            return False
+        return ((allowWeak or not etag.weak) and
+                ([etagmatch for etagmatch in tags if etag.match(etagmatch, strongCompare=not allowWeak)]))
+
+    # First check if-match/if-unmodified-since
+    # If either one fails, we return PRECONDITION_FAILED
+    match = request.headers.getHeader("if-match")
+    if match:
+        if not matchETag(match, False):
+            raise HTTPError(StatusResponse(responsecode.PRECONDITION_FAILED, "Requested resource does not have a matching ETag."))
+
+    unmod_since = request.headers.getHeader("if-unmodified-since")
+    if unmod_since:
+        if not lastModified or lastModified > unmod_since:
+            raise HTTPError(StatusResponse(responsecode.PRECONDITION_FAILED, "Requested resource has changed."))
+
+    # Now check if-none-match/if-modified-since.
+    # This bit is tricky, because of the requirements when both IMS and INM
+    # are present. In that case, you can't return a failure code
+    # unless *both* checks think it failed.
+    # Also, if the INM check succeeds, ignore IMS, because INM is treated
+    # as more reliable.
+
+    # I hope I got the logic right here...the RFC is quite poorly written
+    # in this area. Someone might want to verify the testcase against
+    # RFC wording.
+
+    # If IMS header is later than current time, ignore it.
+    notModified = None
+    ims = request.headers.getHeader('if-modified-since')
+    if ims:
+        notModified = (ims < time.time() and lastModified and lastModified <= ims)
+
+    inm = request.headers.getHeader("if-none-match")
+    if inm:
+        if request.method in ("HEAD", "GET"):
+            # If it's a range request, don't allow a weak ETag, as that
+            # would break. 
+            canBeWeak = not request.headers.hasHeader('Range')
+            if notModified != False and matchETag(inm, canBeWeak):
+                raise HTTPError(NotModifiedResponse(response))
+        else:
+            if notModified != False and matchETag(inm, False):
+                raise HTTPError(StatusResponse(responsecode.PRECONDITION_FAILED, "Requested resource has a matching ETag."))
+    else:
+        if notModified == True:
+            if request.method in ("HEAD", "GET"):
+                raise HTTPError(NotModifiedResponse(response))
+            else:
+                # S14.25 doesn't actually say what to do for a failing IMS on
+                # non-GET methods. But Precondition Failed makes sense to me.
+                raise HTTPError(StatusResponse(responsecode.PRECONDITION_FAILED, "Requested resource has not changed."))
+
+def checkIfRange(request, response):
+    """Checks for the If-Range header, and if it exists, checks if the
+    test passes. Returns true if the server should return partial data."""
+
+    ifrange = request.headers.getHeader("if-range")
+
+    if ifrange is None:
+        return True
+    if isinstance(ifrange, http_headers.ETag):
+        return ifrange.match(response.headers.getHeader("etag"), strongCompare=True)
+    else:
+        return ifrange == response.headers.getHeader("last-modified")
+
+
+class _NotifyingProducerStream(stream.ProducerStream):
+    doStartReading = None
+
+    def __init__(self, length=None, doStartReading=None):
+        stream.ProducerStream.__init__(self, length=length)
+        self.doStartReading = doStartReading
+    
+    def read(self):
+        if self.doStartReading is not None:
+            doStartReading = self.doStartReading
+            self.doStartReading = None
+            doStartReading()
+            
+        return stream.ProducerStream.read(self)
+
+    def write(self, data):
+        self.doStartReading = None
+        stream.ProducerStream.write(self, data)
+
+    def finish(self):
+        self.doStartReading = None
+        stream.ProducerStream.finish(self)
+
+
+# response codes that must have empty bodies
+NO_BODY_CODES = (responsecode.NO_CONTENT, responsecode.NOT_MODIFIED)
+
+class Request(object):
+    """A HTTP request.
+
+    Subclasses should override the process() method to determine how
+    the request will be processed.
+    
+    @ivar method: The HTTP method that was used.
+    @ivar uri: The full URI that was requested (includes arguments).
+    @ivar headers: All received headers
+    @ivar clientproto: client HTTP version
+    @ivar stream: incoming data stream.
+    """
+    
+    implements(iweb.IRequest, interfaces.IConsumer)
+    
+    known_expects = ('100-continue',)
+    
+    def __init__(self, chanRequest, command, path, version, contentLength, headers):
+        """
+        @param chanRequest: the channel request we're associated with.
+        """
+        self.chanRequest = chanRequest
+        self.method = command
+        self.uri = path
+        self.clientproto = version
+        
+        self.headers = headers
+        
+        if '100-continue' in self.headers.getHeader('expect', ()):
+            doStartReading = self._sendContinue
+        else:
+            doStartReading = None
+        self.stream = _NotifyingProducerStream(contentLength, doStartReading)
+        self.stream.registerProducer(self.chanRequest, True)
+        
+    def checkExpect(self):
+        """Ensure there are no expectations that cannot be met.
+        Checks Expect header against self.known_expects."""
+        expects = self.headers.getHeader('expect', ())
+        for expect in expects:
+            if expect not in self.known_expects:
+                raise HTTPError(responsecode.EXPECTATION_FAILED)
+    
+    def process(self):
+        """Called by channel to let you process the request.
+        
+        Can be overridden by a subclass to do something useful."""
+        pass
+    
+    def handleContentChunk(self, data):
+        """Callback from channel when a piece of data has been received.
+        Puts the data in .stream"""
+        self.stream.write(data)
+    
+    def handleContentComplete(self):
+        """Callback from channel when all data has been received. """
+        self.stream.unregisterProducer()
+        self.stream.finish()
+        
+    def connectionLost(self, reason):
+        """connection was lost"""
+        pass
+
+    def __repr__(self):
+        return '<%s %s %s>'% (self.method, self.uri, self.clientproto)
+
+    def _sendContinue(self):
+        self.chanRequest.writeIntermediateResponse(responsecode.CONTINUE)
+
+    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):
+            log.msg("Request error: " + reason.getErrorMessage())
+        else:
+            log.err(reason)
+            # Only bother with cleanup on errors other than lost connection.
+            self.chanRequest.abortConnection()
+        
+    def writeResponse(self, response):
+        """
+        Write a response.
+        """
+        if self.stream.doStartReading is not None:
+            # Expect: 100-continue was requested, but 100 response has not been
+            # sent, and there's a possibility that data is still waiting to be
+            # sent.
+            # 
+            # Ideally this means the remote side will not send any data.
+            # However, because of compatibility requirements, it might timeout,
+            # and decide to do so anyways at the same time we're sending back 
+            # this response. Thus, the read state is unknown after this.
+            # We must close the connection.
+            self.chanRequest.channel.setReadPersistent(False)
+            # Nothing more will be read
+            self.chanRequest.allContentReceived()
+
+        if response.code != responsecode.NOT_MODIFIED:
+            # Not modified response is *special* and doesn't get a content-length.
+            if response.stream is None:
+                response.headers.setHeader('content-length', 0)
+            elif response.stream.length is not None:
+                response.headers.setHeader('content-length', response.stream.length)
+        self.chanRequest.writeHeaders(response.code, response.headers)
+        
+        # if this is a "HEAD" request, or a special response code,
+        # don't return any data.
+        if self.method == "HEAD" or response.code in NO_BODY_CODES:
+            if response.stream is not None:
+                response.stream.close()
+            self._finished(None)
+            return
+            
+        d = stream.StreamProducer(response.stream).beginProducing(self.chanRequest)
+        d.addCallback(self._finished).addErrback(self._error)
+
 class XMLResponse (Response):
     """
     XML L{Response} object.
@@ -36,4 +499,21 @@
         @param xml_responses: an iterable of davxml.Response objects.
         """
         Response.__init__(self, code, stream=element.toxml())
-        self.headers.setHeader("content-type", MimeType("text", "xml"))
+        self.headers.setHeader("content-type", http_headers.MimeType("text", "xml"))
+
+    
+from twext.web2 import compat
+components.registerAdapter(compat.makeOldRequestAdapter, iweb.IRequest, iweb.IOldRequest)
+components.registerAdapter(compat.OldNevowResourceAdapter, iweb.IOldNevowResource, iweb.IResource)
+components.registerAdapter(Response, int, iweb.IResponse)
+
+try:
+    # If twisted.web is installed, add an adapter for it
+    from twisted.web import resource
+except:
+    pass
+else:
+    components.registerAdapter(compat.OldResourceAdapter, resource.IResource, iweb.IOldNevowResource)
+
+__all__ = ['HTTPError', 'NotModifiedResponse', 'Request', 'Response', 'StatusResponse', 'RedirectResponse', 'checkIfRange', 'checkPreconditions', 'defaultPortForScheme', 'parseVersion', 'splitHostPort', "XMLResponse"]
+

Copied: CalendarServer/trunk/twext/web2/http_headers.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/http_headers.py)
===================================================================
--- CalendarServer/trunk/twext/web2/http_headers.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/http_headers.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,1618 @@
+# -*- test-case-name: twext.web2.test.test_http_headers -*-
+##
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.
+#
+##
+
+"""
+HTTP header representation, parsing, and serialization.
+"""
+
+import time
+from calendar import timegm
+import base64
+import re
+
+def dashCapitalize(s):
+    ''' Capitalize a string, making sure to treat - as a word seperator '''
+    return '-'.join([ x.capitalize() for x in s.split('-')])
+
+# datetime parsing and formatting
+weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+weekdayname_lower = [name.lower() for name in weekdayname]
+monthname = [None,
+             'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+             'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+monthname_lower = [name and name.lower() for name in monthname]
+
+# HTTP Header parsing API
+
+header_case_mapping = {}
+
+def casemappingify(d):
+    global header_case_mapping
+    newd = dict([(key.lower(),key) for key in d.keys()])
+    header_case_mapping.update(newd)
+
+def lowerify(d):
+    return dict([(key.lower(),value) for key,value in d.items()])
+
+
+class HeaderHandler(object):
+    """HeaderHandler manages header generating and parsing functions.
+    """
+    HTTPParsers = {}
+    HTTPGenerators = {}
+
+    def __init__(self, parsers=None, generators=None):
+        """
+        @param parsers: A map of header names to parsing functions.
+        @type parsers: L{dict}
+
+        @param generators: A map of header names to generating functions.
+        @type generators: L{dict}
+        """
+
+        if parsers:
+            self.HTTPParsers.update(parsers)
+        if generators:
+            self.HTTPGenerators.update(generators)
+
+    def parse(self, name, header):
+        """
+        Parse the given header based on its given name.
+
+        @param name: The header name to parse.
+        @type name: C{str}
+
+        @param header: A list of unparsed headers.
+        @type header: C{list} of C{str}
+
+        @return: The return value is the parsed header representation,
+            it is dependent on the header.  See the HTTP Headers document.
+        """
+        parser = self.HTTPParsers.get(name, None)
+        if parser is None:
+            raise ValueError("No header parser for header '%s', either add one or use getHeaderRaw." % (name,))
+
+        try:
+            for p in parser:
+                # print "Parsing %s: %s(%s)" % (name, repr(p), repr(h))
+                header = p(header)
+                # if isinstance(h, types.GeneratorType):
+                #     h=list(h)
+        except ValueError,v:
+            # print v
+            header=None
+
+        return header
+
+    def generate(self, name, header):
+        """
+        Generate the given header based on its given name.
+
+        @param name: The header name to generate.
+        @type name: C{str}
+
+        @param header: A parsed header, such as the output of
+            L{HeaderHandler}.parse.
+
+        @return: C{list} of C{str} each representing a generated HTTP header.
+        """
+        generator = self.HTTPGenerators.get(name, None)
+
+        if generator is None:
+            # print self.generators
+            raise ValueError("No header generator for header '%s', either add one or use setHeaderRaw." % (name,))
+
+        for g in generator:
+            header = g(header)
+
+        #self._raw_headers[name] = h
+        return header
+
+    def updateParsers(self, parsers):
+        """Update en masse the parser maps.
+
+        @param parsers: Map of header names to parser chains.
+        @type parsers: C{dict}
+        """
+        casemappingify(parsers)
+        self.HTTPParsers.update(lowerify(parsers))
+
+    def addParser(self, name, value):
+        """Add an individual parser chain for the given header.
+
+        @param name: Name of the header to add
+        @type name: C{str}
+
+        @param value: The parser chain
+        @type value: C{str}
+        """
+        self.updateParsers({name: value})
+
+    def updateGenerators(self, generators):
+        """Update en masse the generator maps.
+
+        @param parsers: Map of header names to generator chains.
+        @type parsers: C{dict}
+        """
+        casemappingify(generators)
+        self.HTTPGenerators.update(lowerify(generators))
+
+    def addGenerators(self, name, value):
+        """Add an individual generator chain for the given header.
+
+        @param name: Name of the header to add
+        @type name: C{str}
+
+        @param value: The generator chain
+        @type value: C{str}
+        """
+        self.updateGenerators({name: value})
+
+    def update(self, parsers, generators):
+        """Conveniently update parsers and generators all at once.
+        """
+        self.updateParsers(parsers)
+        self.updateGenerators(generators)
+
+
+DefaultHTTPHandler = HeaderHandler()
+
+
+## HTTP DateTime parser
+def parseDateTime(dateString):
+    """Convert an HTTP date string (one of three formats) to seconds since epoch."""
+    parts = dateString.split()
+
+    if not parts[0][0:3].lower() in weekdayname_lower:
+        # Weekday is stupid. Might have been omitted.
+        try:
+            return parseDateTime("Sun, "+dateString)
+        except ValueError:
+            # Guess not.
+            pass
+
+    partlen = len(parts)
+    if (partlen == 5 or partlen == 6) and parts[1].isdigit():
+        # 1st date format: Sun, 06 Nov 1994 08:49:37 GMT
+        # (Note: "GMT" is literal, not a variable timezone)
+        # (also handles without "GMT")
+        # This is the normal format
+        day = parts[1]
+        month = parts[2]
+        year = parts[3]
+        time = parts[4]
+    elif (partlen == 3 or partlen == 4) and parts[1].find('-') != -1:
+        # 2nd date format: Sunday, 06-Nov-94 08:49:37 GMT
+        # (Note: "GMT" is literal, not a variable timezone)
+        # (also handles without without "GMT")
+        # Two digit year, yucko.
+        day, month, year = parts[1].split('-')
+        time = parts[2]
+        year=int(year)
+        if year < 69:
+            year = year + 2000
+        elif year < 100:
+            year = year + 1900
+    elif len(parts) == 5:
+        # 3rd date format: Sun Nov  6 08:49:37 1994
+        # ANSI C asctime() format.
+        day = parts[2]
+        month = parts[1]
+        year = parts[4]
+        time = parts[3]
+    else:
+        raise ValueError("Unknown datetime format %r" % dateString)
+
+    day = int(day)
+    month = int(monthname_lower.index(month.lower()))
+    year = int(year)
+    hour, min, sec = map(int, time.split(':'))
+    return int(timegm((year, month, day, hour, min, sec)))
+
+
+##### HTTP tokenizer
+class Token(str):
+    __slots__=[]
+    tokens = {}
+    def __new__(self, char):
+        token = Token.tokens.get(char)
+        if token is None:
+            Token.tokens[char] = token = str.__new__(self, char)
+        return token
+
+    def __repr__(self):
+        return "Token(%s)" % str.__repr__(self)
+
+
+# RFC 2616 section 2.2
+http_tokens = " \t\"()<>@,;:\\/[]?={}"
+http_ctls = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f"
+
+def tokenize(header, foldCase=True):
+    """Tokenize a string according to normal HTTP header parsing rules.
+
+    In particular:
+     - Whitespace is irrelevant and eaten next to special separator tokens.
+       Its existance (but not amount) is important between character strings.
+     - Quoted string support including embedded backslashes.
+     - Case is insignificant (and thus lowercased), except in quoted strings.
+        (unless foldCase=False)
+     - Multiple headers are concatenated with ','
+
+    NOTE: not all headers can be parsed with this function.
+
+    Takes a raw header value (list of strings), and
+    Returns a generator of strings and Token class instances.
+    """
+    tokens=http_tokens
+    ctls=http_ctls
+
+    string = ",".join(header)
+    list = []
+    start = 0
+    cur = 0
+    quoted = False
+    qpair = False
+    inSpaces = -1
+    qstring = None
+
+    for x in string:
+        if quoted:
+            if qpair:
+                qpair = False
+                qstring = qstring+string[start:cur-1]+x
+                start = cur+1
+            elif x == '\\':
+                qpair = True
+            elif x == '"':
+                quoted = False
+                yield qstring+string[start:cur]
+                qstring=None
+                start = cur+1
+        elif x in tokens:
+            if start != cur:
+                if foldCase:
+                    yield string[start:cur].lower()
+                else:
+                    yield string[start:cur]
+
+            start = cur+1
+            if x == '"':
+                quoted = True
+                qstring = ""
+                inSpaces = False
+            elif x in " \t":
+                if inSpaces is False:
+                    inSpaces = True
+            else:
+                inSpaces = -1
+                yield Token(x)
+        elif x in ctls:
+            raise ValueError("Invalid control character: %d in header" % ord(x))
+        else:
+            if inSpaces is True:
+                yield Token(' ')
+                inSpaces = False
+
+            inSpaces = False
+        cur = cur+1
+
+    if qpair:
+        raise ValueError, "Missing character after '\\'"
+    if quoted:
+        raise ValueError, "Missing end quote"
+
+    if start != cur:
+        if foldCase:
+            yield string[start:cur].lower()
+        else:
+            yield string[start:cur]
+
+def split(seq, delim):
+    """The same as str.split but works on arbitrary sequences.
+    Too bad it's not builtin to python!"""
+
+    cur = []
+    for item in seq:
+        if item == delim:
+            yield cur
+            cur = []
+        else:
+            cur.append(item)
+    yield cur
+
+# def find(seq, *args):
+#     """The same as seq.index but returns -1 if not found, instead
+#     Too bad it's not builtin to python!"""
+#     try:
+#         return seq.index(value, *args)
+#     except ValueError:
+#         return -1
+
+
+def filterTokens(seq):
+    """Filter out instances of Token, leaving only a list of strings.
+
+    Used instead of a more specific parsing method (e.g. splitting on commas)
+    when only strings are expected, so as to be a little lenient.
+
+    Apache does it this way and has some comments about broken clients which
+    forget commas (?), so I'm doing it the same way. It shouldn't
+    hurt anything, in any case.
+    """
+
+    l=[]
+    for x in seq:
+        if not isinstance(x, Token):
+            l.append(x)
+    return l
+
+##### parser utilities:
+def checkSingleToken(tokens):
+    if len(tokens) != 1:
+        raise ValueError, "Expected single token, not %s." % (tokens,)
+    return tokens[0]
+
+def parseKeyValue(val):
+    if len(val) == 1:
+        return val[0],None
+    elif len(val) == 3 and val[1] == Token('='):
+        return val[0],val[2]
+    raise ValueError, "Expected key or key=value, but got %s." % (val,)
+
+def parseArgs(field):
+    args=split(field, Token(';'))
+    val = args.next()
+    args = [parseKeyValue(arg) for arg in args]
+    return val,args
+
+def listParser(fun):
+    """Return a function which applies 'fun' to every element in the
+    comma-separated list"""
+    def listParserHelper(tokens):
+        fields = split(tokens, Token(','))
+        for field in fields:
+            if len(field) != 0:
+                yield fun(field)
+
+    return listParserHelper
+
+def last(seq):
+    """Return seq[-1]"""
+
+    return seq[-1]
+
+##### Generation utilities
+def quoteString(s):
+    """
+    Quote a string according to the rules for the I{quoted-string} production
+    in RFC 2616 section 2.2.
+
+    @type s: C{str}
+    @rtype: C{str}
+    """
+    return '"%s"' % s.replace('\\', '\\\\').replace('"', '\\"')
+
+def listGenerator(fun):
+    """Return a function which applies 'fun' to every element in
+    the given list, then joins the result with generateList"""
+    def listGeneratorHelper(l):
+        return generateList([fun(e) for e in l])
+
+    return listGeneratorHelper
+
+def generateList(seq):
+    return ", ".join(seq)
+
+def singleHeader(item):
+    return [item]
+
+_seperators = re.compile('[' + re.escape(http_tokens) + ']')
+
+def generateKeyValues(parameters):
+    """
+    Format an iterable of key/value pairs.
+
+    Although each header in HTTP 1.1 redefines the grammar for the formatting
+    of its parameters, the grammar defined by almost all headers conforms to
+    the specification given in RFC 2046.  Note also that RFC 2616 section 19.2
+    note 2 points out that many implementations fail if the value is quoted,
+    therefore this function only quotes the value when it is necessary.
+
+    @param parameters: An iterable of C{tuple} of a C{str} parameter name and
+        C{str} or C{None} parameter value which will be formated.
+
+    @return: The formatted result.
+    @rtype: C{str}
+    """
+    l = []
+    for k, v in parameters:
+        if v is None:
+            l.append('%s' % k)
+        else:
+            if _seperators.search(v) is not None:
+                v = quoteString(v)
+            l.append('%s=%s' % (k, v))
+    return ";".join(l)
+
+
+class MimeType(object):
+    def fromString(klass, mimeTypeString):
+        """Generate a MimeType object from the given string.
+
+        @param mimeTypeString: The mimetype to parse
+
+        @return: L{MimeType}
+        """
+        return DefaultHTTPHandler.parse('content-type', [mimeTypeString])
+
+    fromString = classmethod(fromString)
+
+    def __init__(self, mediaType, mediaSubtype, params={}, **kwargs):
+        """
+        @type mediaType: C{str}
+
+        @type mediaSubtype: C{str}
+
+        @type params: C{dict}
+        """
+        self.mediaType = mediaType
+        self.mediaSubtype = mediaSubtype
+        self.params = dict(params)
+
+        if kwargs:
+            self.params.update(kwargs)
+
+    def __eq__(self, other):
+        if not isinstance(other, MimeType): return NotImplemented
+        return (self.mediaType == other.mediaType and
+                self.mediaSubtype == other.mediaSubtype and
+                self.params == other.params)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __repr__(self):
+        return "MimeType(%r, %r, %r)" % (self.mediaType, self.mediaSubtype, self.params)
+
+    def __hash__(self):
+        return hash(self.mediaType)^hash(self.mediaSubtype)^hash(tuple(self.params.iteritems()))
+
+##### Specific header parsers.
+def parseAccept(field):
+    type,args = parseArgs(field)
+
+    if len(type) != 3 or type[1] != Token('/'):
+        raise ValueError, "MIME Type "+str(type)+" invalid."
+
+    # okay, this spec is screwy. A 'q' parameter is used as the separator
+    # between MIME parameters and (as yet undefined) additional HTTP
+    # parameters.
+
+    num = 0
+    for arg in args:
+        if arg[0] == 'q':
+            mimeparams=tuple(args[0:num])
+            params=args[num:]
+            break
+        num = num + 1
+    else:
+        mimeparams=tuple(args)
+        params=[]
+
+    # Default values for parameters:
+    qval = 1.0
+
+    # Parse accept parameters:
+    for param in params:
+        if param[0] =='q':
+            qval = float(param[1])
+        else:
+            # Warn? ignored parameter.
+            pass
+
+    ret = MimeType(type[0],type[2],mimeparams),qval
+    return ret
+
+def parseAcceptQvalue(field):
+    type,args=parseArgs(field)
+
+    type = checkSingleToken(type)
+
+    qvalue = 1.0 # Default qvalue is 1
+    for arg in args:
+        if arg[0] == 'q':
+            qvalue = float(arg[1])
+    return type,qvalue
+
+def addDefaultCharset(charsets):
+    if charsets.get('*') is None and charsets.get('iso-8859-1') is None:
+        charsets['iso-8859-1'] = 1.0
+    return charsets
+
+def addDefaultEncoding(encodings):
+    if encodings.get('*') is None and encodings.get('identity') is None:
+        # RFC doesn't specify a default value for identity, only that it
+        # "is acceptable" if not mentioned. Thus, give it a very low qvalue.
+        encodings['identity'] = .0001
+    return encodings
+
+
+def parseContentType(header):
+    # Case folding is disabled for this header, because of use of
+    # Content-Type: multipart/form-data; boundary=CaSeFuLsTuFf
+    # So, we need to explicitly .lower() the type/subtype and arg keys.
+
+    type,args = parseArgs(header)
+
+    if len(type) != 3 or type[1] != Token('/'):
+        raise ValueError, "MIME Type "+str(type)+" invalid."
+
+    args = [(kv[0].lower(), kv[1]) for kv in args]
+
+    return MimeType(type[0].lower(), type[2].lower(), tuple(args))
+
+def parseContentMD5(header):
+    try:
+        return base64.decodestring(header)
+    except Exception,e:
+        raise ValueError(e)
+
+def parseContentRange(header):
+    """Parse a content-range header into (kind, start, end, realLength).
+
+    realLength might be None if real length is not known ('*').
+    start and end might be None if start,end unspecified (for response code 416)
+    """
+    kind, other = header.strip().split()
+    if kind.lower() != "bytes":
+        raise ValueError("a range of type %r is not supported")
+    startend, realLength = other.split("/")
+    if startend.strip() == '*':
+        start,end=None,None
+    else:
+        start, end = map(int, startend.split("-"))
+    if realLength == "*":
+        realLength = None
+    else:
+        realLength = int(realLength)
+    return (kind, start, end, realLength)
+
+def parseExpect(field):
+    type,args=parseArgs(field)
+
+    type=parseKeyValue(type)
+    return (type[0], (lambda *args:args)(type[1], *args))
+
+def parseExpires(header):
+    # """HTTP/1.1 clients and caches MUST treat other invalid date formats,
+    #    especially including the value 0, as in the past (i.e., "already expired")."""
+
+    try:
+        return parseDateTime(header)
+    except ValueError:
+        return 0
+
+def parseIfModifiedSince(header):
+    # Ancient versions of netscape and *current* versions of MSIE send
+    #   If-Modified-Since: Thu, 05 Aug 2004 12:57:27 GMT; length=123
+    # which is blantantly RFC-violating and not documented anywhere
+    # except bug-trackers for web frameworks.
+
+    # So, we'll just strip off everything after a ';'.
+    return parseDateTime(header.split(';', 1)[0])
+
+def parseIfRange(headers):
+    try:
+        return ETag.parse(tokenize(headers))
+    except ValueError:
+        return parseDateTime(last(headers))
+
+def parseRange(range):
+    range = list(range)
+    if len(range) < 3 or range[1] != Token('='):
+        raise ValueError("Invalid range header format: %s" %(range,))
+
+    type=range[0]
+    if type != 'bytes':
+        raise ValueError("Unknown range unit: %s." % (type,))
+    rangeset=split(range[2:], Token(','))
+    ranges = []
+
+    for byterangespec in rangeset:
+        if len(byterangespec) != 1:
+            raise ValueError("Invalid range header format: %s" % (range,))
+        start,end=byterangespec[0].split('-')
+
+        if not start and not end:
+            raise ValueError("Invalid range header format: %s" % (range,))
+
+        if start:
+            start = int(start)
+        else:
+            start = None
+
+        if end:
+            end = int(end)
+        else:
+            end = None
+
+        if start and end and start > end:
+            raise ValueError("Invalid range header, start > end: %s" % (range,))
+        ranges.append((start,end))
+    return type,ranges
+
+def parseRetryAfter(header):
+    try:
+        # delta seconds
+        return time.time() + int(header)
+    except ValueError:
+        # or datetime
+        return parseDateTime(header)
+
+# WWW-Authenticate and Authorization
+
+def parseWWWAuthenticate(tokenized):
+    headers = []
+
+    tokenList = list(tokenized)
+
+    while tokenList:
+        scheme = tokenList.pop(0)
+        challenge = {}
+        last = None
+        kvChallenge = False
+
+        while tokenList:
+            token = tokenList.pop(0)
+            if token == Token('='):
+                kvChallenge = True
+                challenge[last] = tokenList.pop(0)
+                last = None
+
+            elif token == Token(','):
+                if kvChallenge:
+                    if len(tokenList) > 1 and tokenList[1] != Token('='):
+                        break
+
+                else:
+                    break
+
+            else:
+                last = token
+
+        if last and scheme and not challenge and not kvChallenge:
+            challenge = last
+            last = None
+
+        headers.append((scheme, challenge))
+
+    if last and last not in (Token('='), Token(',')):
+        if headers[-1] == (scheme, challenge):
+            scheme = last
+            challenge = {}
+            headers.append((scheme, challenge))
+
+    return headers
+
+def parseAuthorization(header):
+    scheme, rest = header.split(' ', 1)
+    # this header isn't tokenized because it may eat characters
+    # in the unquoted base64 encoded credentials
+    return scheme.lower(), rest
+
+#### Header generators
+def generateAccept(accept):
+    mimeType,q = accept
+
+    out="%s/%s"%(mimeType.mediaType, mimeType.mediaSubtype)
+    if mimeType.params:
+        out+=';'+generateKeyValues(mimeType.params.iteritems())
+
+    if q != 1.0:
+        out+=(';q=%.3f' % (q,)).rstrip('0').rstrip('.')
+
+    return out
+
+def removeDefaultEncoding(seq):
+    for item in seq:
+        if item[0] != 'identity' or item[1] != .0001:
+            yield item
+
+def generateAcceptQvalue(keyvalue):
+    if keyvalue[1] == 1.0:
+        return "%s" % keyvalue[0:1]
+    else:
+        return ("%s;q=%.3f" % keyvalue).rstrip('0').rstrip('.')
+
+def parseCacheControl(kv):
+    k, v = parseKeyValue(kv)
+    if k == 'max-age' or k == 'min-fresh' or k == 's-maxage':
+        # Required integer argument
+        if v is None:
+            v = 0
+        else:
+            v = int(v)
+    elif k == 'max-stale':
+        # Optional integer argument
+        if v is not None:
+            v = int(v)
+    elif k == 'private' or k == 'no-cache':
+        # Optional list argument
+        if v is not None:
+            v = [field.strip().lower() for field in v.split(',')]
+    return k, v
+
+def generateCacheControl((k, v)):
+    if v is None:
+        return str(k)
+    else:
+        if k == 'no-cache' or k == 'private':
+            # quoted list of values
+            v = quoteString(generateList(
+                [header_case_mapping.get(name) or dashCapitalize(name) for name in v]))
+        return '%s=%s' % (k,v)
+
+def generateContentRange(tup):
+    """tup is (type, start, end, len)
+    len can be None.
+    """
+    type, start, end, len = tup
+    if len == None:
+        len = '*'
+    else:
+        len = int(len)
+    if start == None and end == None:
+        startend = '*'
+    else:
+        startend = '%d-%d' % (start, end)
+
+    return '%s %s/%s' % (type, startend, len)
+
+def generateDateTime(secSinceEpoch):
+    """Convert seconds since epoch to HTTP datetime string."""
+    year, month, day, hh, mm, ss, wd, y, z = time.gmtime(secSinceEpoch)
+    s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
+        weekdayname[wd],
+        day, monthname[month], year,
+        hh, mm, ss)
+    return s
+
+def generateExpect(item):
+    if item[1][0] is None:
+        out = '%s' % (item[0],)
+    else:
+        out = '%s=%s' % (item[0], item[1][0])
+    if len(item[1]) > 1:
+        out += ';'+generateKeyValues(item[1][1:])
+    return out
+
+def generateRange(range):
+    def noneOr(s):
+        if s is None:
+            return ''
+        return s
+
+    type,ranges=range
+
+    if type != 'bytes':
+        raise ValueError("Unknown range unit: "+type+".")
+
+    return (type+'='+
+            ','.join(['%s-%s' % (noneOr(startend[0]), noneOr(startend[1]))
+                      for startend in ranges]))
+
+def generateRetryAfter(when):
+    # always generate delta seconds format
+    return str(int(when - time.time()))
+
+def generateContentType(mimeType):
+    out="%s/%s"%(mimeType.mediaType, mimeType.mediaSubtype)
+    if mimeType.params:
+        out+=';'+generateKeyValues(mimeType.params.iteritems())
+    return out
+
+def generateIfRange(dateOrETag):
+    if isinstance(dateOrETag, ETag):
+        return dateOrETag.generate()
+    else:
+        return generateDateTime(dateOrETag)
+
+# WWW-Authenticate and Authorization
+
+def generateWWWAuthenticate(headers):
+    _generated = []
+    for seq in headers:
+        scheme, challenge = seq[0], seq[1]
+
+        # If we're going to parse out to something other than a dict
+        # we need to be able to generate from something other than a dict
+
+        try:
+            l = []
+            for k,v in dict(challenge).iteritems():
+                l.append("%s=%s" % (k, quoteString(v)))
+
+            _generated.append("%s %s" % (scheme, ", ".join(l)))
+        except ValueError:
+            _generated.append("%s %s" % (scheme, challenge))
+
+    return _generated
+
+def generateAuthorization(seq):
+    return [' '.join(seq)]
+
+
+####
+class ETag(object):
+    def __init__(self, tag, weak=False):
+        self.tag = str(tag)
+        self.weak = weak
+
+    def match(self, other, strongCompare):
+        # Sec 13.3.
+        # The strong comparison function: in order to be considered equal, both
+        #   validators MUST be identical in every way, and both MUST NOT be weak.
+        #
+        # The weak comparison function: in order to be considered equal, both
+        #   validators MUST be identical in every way, but either or both of
+        #   them MAY be tagged as "weak" without affecting the result.
+
+        if not isinstance(other, ETag) or other.tag != self.tag:
+            return False
+
+        if strongCompare and (other.weak or self.weak):
+            return False
+        return True
+
+    def __eq__(self, other):
+        return isinstance(other, ETag) and other.tag == self.tag and other.weak == self.weak
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __repr__(self):
+        return "Etag(%r, weak=%r)" % (self.tag, self.weak)
+
+    def parse(tokens):
+        tokens=tuple(tokens)
+        if len(tokens) == 1 and not isinstance(tokens[0], Token):
+            return ETag(tokens[0])
+
+        if(len(tokens) == 3 and tokens[0] == "w"
+           and tokens[1] == Token('/')):
+            return ETag(tokens[2], weak=True)
+
+        raise ValueError("Invalid ETag.")
+
+    parse=staticmethod(parse)
+
+    def generate(self):
+        if self.weak:
+            return 'W/'+quoteString(self.tag)
+        else:
+            return quoteString(self.tag)
+
+def parseStarOrETag(tokens):
+    tokens=tuple(tokens)
+    if tokens == ('*',):
+        return '*'
+    else:
+        return ETag.parse(tokens)
+
+def generateStarOrETag(etag):
+    if etag=='*':
+        return etag
+    else:
+        return etag.generate()
+
+#### Cookies. Blech!
+class Cookie(object):
+    # __slots__ = ['name', 'value', 'path', 'domain', 'ports', 'expires', 'discard', 'secure', 'comment', 'commenturl', 'version']
+
+    def __init__(self, name, value, path=None, domain=None, ports=None, expires=None, discard=False, secure=False, comment=None, commenturl=None, version=0):
+        self.name=name
+        self.value=value
+        self.path=path
+        self.domain=domain
+        self.ports=ports
+        self.expires=expires
+        self.discard=discard
+        self.secure=secure
+        self.comment=comment
+        self.commenturl=commenturl
+        self.version=version
+
+    def __repr__(self):
+        s="Cookie(%r=%r" % (self.name, self.value)
+        if self.path is not None: s+=", path=%r" % (self.path,)
+        if self.domain is not None: s+=", domain=%r" % (self.domain,)
+        if self.ports is not None: s+=", ports=%r" % (self.ports,)
+        if self.expires is not None: s+=", expires=%r" % (self.expires,)
+        if self.secure is not False: s+=", secure=%r" % (self.secure,)
+        if self.comment is not None: s+=", comment=%r" % (self.comment,)
+        if self.commenturl is not None: s+=", commenturl=%r" % (self.commenturl,)
+        if self.version != 0: s+=", version=%r" % (self.version,)
+        s+=")"
+        return s
+
+    def __eq__(self, other):
+        return (isinstance(other, Cookie) and
+                other.path == self.path and
+                other.domain == self.domain and
+                other.ports == self.ports and
+                other.expires == self.expires and
+                other.secure == self.secure and
+                other.comment == self.comment and
+                other.commenturl == self.commenturl and
+                other.version == self.version)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+
+def parseCookie(headers):
+    """Bleargh, the cookie spec sucks.
+    This surely needs interoperability testing.
+    There are two specs that are supported:
+    Version 0) http://wp.netscape.com/newsref/std/cookie_spec.html
+    Version 1) http://www.faqs.org/rfcs/rfc2965.html
+    """
+
+    cookies = []
+    # There can't really be multiple cookie headers according to RFC, because
+    # if multiple headers are allowed, they must be joinable with ",".
+    # Neither new RFC2965 cookies nor old netscape cookies are.
+
+    header = ';'.join(headers)
+    if header[0:8].lower() == "$version":
+        # RFC2965 cookie
+        h=tokenize([header], foldCase=False)
+        r_cookies = split(h, Token(','))
+        for r_cookie in r_cookies:
+            last_cookie = None
+            rr_cookies = split(r_cookie, Token(';'))
+            for cookie in rr_cookies:
+                nameval = tuple(split(cookie, Token('=')))
+                if len(nameval) == 2:
+                    (name,), (value,) = nameval
+                else:
+                    (name,), = nameval
+                    value = None
+
+                name=name.lower()
+                if name == '$version':
+                    continue
+                if name[0] == '$':
+                    if last_cookie is not None:
+                        if name == '$path':
+                            last_cookie.path=value
+                        elif name == '$domain':
+                            last_cookie.domain=value
+                        elif name == '$port':
+                            if value is None:
+                                last_cookie.ports = ()
+                            else:
+                                last_cookie.ports=tuple([int(s) for s in value.split(',')])
+                else:
+                    last_cookie = Cookie(name, value, version=1)
+                    cookies.append(last_cookie)
+    else:
+        # Oldstyle cookies don't do quoted strings or anything sensible.
+        # All characters are valid for names except ';' and '=', and all
+        # characters are valid for values except ';'. Spaces are stripped,
+        # however.
+        r_cookies = header.split(';')
+        for r_cookie in r_cookies:
+            name,value = r_cookie.split('=', 1)
+            name=name.strip(' \t')
+            value=value.strip(' \t')
+
+            cookies.append(Cookie(name, value))
+
+    return cookies
+
+cookie_validname = "[^"+re.escape(http_tokens+http_ctls)+"]*$"
+cookie_validname_re = re.compile(cookie_validname)
+cookie_validvalue = cookie_validname+'|"([^"]|\\\\")*"$'
+cookie_validvalue_re = re.compile(cookie_validvalue)
+
+def generateCookie(cookies):
+    # There's a fundamental problem with the two cookie specifications.
+    # They both use the "Cookie" header, and the RFC Cookie header only allows
+    # one version to be specified. Thus, when you have a collection of V0 and
+    # V1 cookies, you have to either send them all as V0 or send them all as
+    # V1.
+
+    # I choose to send them all as V1.
+
+    # You might think converting a V0 cookie to a V1 cookie would be lossless,
+    # but you'd be wrong. If you do the conversion, and a V0 parser tries to
+    # read the cookie, it will see a modified form of the cookie, in cases
+    # where quotes must be added to conform to proper V1 syntax.
+    # (as a real example: "Cookie: cartcontents=oid:94680,qty:1,auto:0,esp:y")
+
+    # However, that is what we will do, anyways. It has a high probability of
+    # breaking applications that only handle oldstyle cookies, where some other
+    # application set a newstyle cookie that is applicable over for site
+    # (or host), AND where the oldstyle cookie uses a value which is invalid
+    # syntax in a newstyle cookie.
+
+    # Also, the cookie name *cannot* be quoted in V1, so some cookies just
+    # cannot be converted at all. (e.g. "Cookie: phpAds_capAd[32]=2"). These
+    # are just dicarded during conversion.
+
+    # As this is an unsolvable problem, I will pretend I can just say
+    # OH WELL, don't do that, or else upgrade your old applications to have
+    # newstyle cookie parsers.
+
+    # I will note offhandedly that there are *many* sites which send V0 cookies
+    # that are not valid V1 cookie syntax. About 20% for my cookies file.
+    # However, they do not generally mix them with V1 cookies, so this isn't
+    # an issue, at least right now. I have not tested to see how many of those
+    # webapps support RFC2965 V1 cookies. I suspect not many.
+
+    max_version = max([cookie.version for cookie in cookies])
+
+    if max_version == 0:
+        # no quoting or anything.
+        return ';'.join(["%s=%s" % (cookie.name, cookie.value) for cookie in cookies])
+    else:
+        str_cookies = ['$Version="1"']
+        for cookie in cookies:
+            if cookie.version == 0:
+                # Version 0 cookie: we make sure the name and value are valid
+                # V1 syntax.
+
+                # If they are, we use them as is. This means in *most* cases,
+                # the cookie will look literally the same on output as it did
+                # on input.
+                # If it isn't a valid name, ignore the cookie.
+                # If it isn't a valid value, quote it and hope for the best on
+                # the other side.
+
+                if cookie_validname_re.match(cookie.name) is None:
+                    continue
+
+                value=cookie.value
+                if cookie_validvalue_re.match(cookie.value) is None:
+                    value = quoteString(value)
+
+                str_cookies.append("%s=%s" % (cookie.name, value))
+            else:
+                # V1 cookie, nice and easy
+                str_cookies.append("%s=%s" % (cookie.name, quoteString(cookie.value)))
+
+            if cookie.path:
+                str_cookies.append("$Path=%s" % quoteString(cookie.path))
+            if cookie.domain:
+                str_cookies.append("$Domain=%s" % quoteString(cookie.domain))
+            if cookie.ports is not None:
+                if len(cookie.ports) == 0:
+                    str_cookies.append("$Port")
+                else:
+                    str_cookies.append("$Port=%s" % quoteString(",".join([str(x) for x in cookie.ports])))
+        return ';'.join(str_cookies)
+
+def parseSetCookie(headers):
+    setCookies = []
+    for header in headers:
+        try:
+            parts = header.split(';')
+            l = []
+
+            for part in parts:
+                namevalue = part.split('=',1)
+                if len(namevalue) == 1:
+                    name=namevalue[0]
+                    value=None
+                else:
+                    name,value=namevalue
+                    value=value.strip(' \t')
+
+                name=name.strip(' \t')
+
+                l.append((name, value))
+
+            setCookies.append(makeCookieFromList(l, True))
+        except ValueError:
+            # If we can't parse one Set-Cookie, ignore it,
+            # but not the rest of Set-Cookies.
+            pass
+    return setCookies
+
+def parseSetCookie2(toks):
+    outCookies = []
+    for cookie in [[parseKeyValue(x) for x in split(y, Token(';'))]
+                   for y in split(toks, Token(','))]:
+        try:
+            outCookies.append(makeCookieFromList(cookie, False))
+        except ValueError:
+            # Again, if we can't handle one cookie -- ignore it.
+            pass
+    return outCookies
+
+def makeCookieFromList(tup, netscapeFormat):
+    name, value = tup[0]
+    if name is None or value is None:
+        raise ValueError("Cookie has missing name or value")
+    if name.startswith("$"):
+        raise ValueError("Invalid cookie name: %r, starts with '$'." % name)
+    cookie = Cookie(name, value)
+    hadMaxAge = False
+
+    for name,value in tup[1:]:
+        name = name.lower()
+
+        if value is None:
+            if name in ("discard", "secure"):
+                # Boolean attrs
+                value = True
+            elif name != "port":
+                # Can be either boolean or explicit
+                continue
+
+        if name in ("comment", "commenturl", "discard", "domain", "path", "secure"):
+            # simple cases
+            setattr(cookie, name, value)
+        elif name == "expires" and not hadMaxAge:
+            if netscapeFormat and value[0] == '"' and value[-1] == '"':
+                value = value[1:-1]
+            cookie.expires = parseDateTime(value)
+        elif name == "max-age":
+            hadMaxAge = True
+            cookie.expires = int(value) + time.time()
+        elif name == "port":
+            if value is None:
+                cookie.ports = ()
+            else:
+                if netscapeFormat and value[0] == '"' and value[-1] == '"':
+                    value = value[1:-1]
+                cookie.ports = tuple([int(s) for s in value.split(',')])
+        elif name == "version":
+            cookie.version = int(value)
+
+    return cookie
+
+
+def generateSetCookie(cookies):
+    setCookies = []
+    for cookie in cookies:
+        out = ["%s=%s" % (cookie.name, cookie.value)]
+        if cookie.expires:
+            out.append("expires=%s" % generateDateTime(cookie.expires))
+        if cookie.path:
+            out.append("path=%s" % cookie.path)
+        if cookie.domain:
+            out.append("domain=%s" % cookie.domain)
+        if cookie.secure:
+            out.append("secure")
+
+        setCookies.append('; '.join(out))
+    return setCookies
+
+def generateSetCookie2(cookies):
+    setCookies = []
+    for cookie in cookies:
+        out = ["%s=%s" % (cookie.name, quoteString(cookie.value))]
+        if cookie.comment:
+            out.append("Comment=%s" % quoteString(cookie.comment))
+        if cookie.commenturl:
+            out.append("CommentURL=%s" % quoteString(cookie.commenturl))
+        if cookie.discard:
+            out.append("Discard")
+        if cookie.domain:
+            out.append("Domain=%s" % quoteString(cookie.domain))
+        if cookie.expires:
+            out.append("Max-Age=%s" % (cookie.expires - time.time()))
+        if cookie.path:
+            out.append("Path=%s" % quoteString(cookie.path))
+        if cookie.ports is not None:
+            if len(cookie.ports) == 0:
+                out.append("Port")
+            else:
+                out.append("Port=%s" % quoteString(",".join([str(x) for x in cookie.ports])))
+        if cookie.secure:
+            out.append("Secure")
+        out.append('Version="1"')
+        setCookies.append('; '.join(out))
+    return setCookies
+
+def parseDepth(depth):
+    if depth not in ("0", "1", "infinity"):
+        raise ValueError("Invalid depth header value: %s" % (depth,))
+    return depth
+
+def parseOverWrite(overwrite):
+    if overwrite == "F":
+        return False
+    elif overwrite == "T":
+        return True
+    raise ValueError("Invalid overwrite header value: %s" % (overwrite,))
+
+def generateOverWrite(overwrite):
+    if overwrite:
+        return "T"
+    else:
+        return "F"
+
+##### Random stuff that looks useful.
+# def sortMimeQuality(s):
+#     def sorter(item1, item2):
+#         if item1[0] == '*':
+#             if item2[0] == '*':
+#                 return 0
+
+
+# def sortQuality(s):
+#     def sorter(item1, item2):
+#         if item1[1] < item2[1]:
+#             return -1
+#         if item1[1] < item2[1]:
+#             return 1
+#         if item1[0] == item2[0]:
+#             return 0
+
+
+# def getMimeQuality(mimeType, accepts):
+#     type,args = parseArgs(mimeType)
+#     type=type.split(Token('/'))
+#     if len(type) != 2:
+#         raise ValueError, "MIME Type "+s+" invalid."
+
+#     for accept in accepts:
+#         accept,acceptQual=accept
+#         acceptType=accept[0:1]
+#         acceptArgs=accept[2]
+
+#         if ((acceptType == type or acceptType == (type[0],'*') or acceptType==('*','*')) and
+#             (args == acceptArgs or len(acceptArgs) == 0)):
+#             return acceptQual
+
+# def getQuality(type, accepts):
+#     qual = accepts.get(type)
+#     if qual is not None:
+#         return qual
+
+#     return accepts.get('*')
+
+# Headers object
+class __RecalcNeeded(object):
+    def __repr__(self):
+        return "<RecalcNeeded>"
+
+_RecalcNeeded = __RecalcNeeded()
+
+class Headers(object):
+    """
+    This class stores the HTTP headers as both a parsed representation
+    and the raw string representation. It converts between the two on
+    demand.
+    """
+
+    def __init__(self, headers=None, rawHeaders=None, handler=DefaultHTTPHandler):
+        self._raw_headers = {}
+        self._headers = {}
+        self.handler = handler
+        if headers is not None:
+            for key, value in headers.iteritems():
+                self.setHeader(key, value)
+        if rawHeaders is not None:
+            for key, value in rawHeaders.iteritems():
+                self.setRawHeaders(key, value)
+
+    def _setRawHeaders(self, headers):
+        self._raw_headers = headers
+        self._headers = {}
+
+    def _toParsed(self, name):
+        r = self._raw_headers.get(name, None)
+        h = self.handler.parse(name, r)
+        if h is not None:
+            self._headers[name] = h
+        return h
+
+    def _toRaw(self, name):
+        h = self._headers.get(name, None)
+        r = self.handler.generate(name, h)
+        if r is not None:
+            self._raw_headers[name] = r
+        return r
+
+    def hasHeader(self, name):
+        """Does a header with the given name exist?"""
+        name=name.lower()
+        return self._raw_headers.has_key(name)
+
+    def getRawHeaders(self, name, default=None):
+        """Returns a list of headers matching the given name as the raw string given."""
+
+        name=name.lower()
+        raw_header = self._raw_headers.get(name, default)
+        if raw_header is not _RecalcNeeded:
+            return raw_header
+
+        return self._toRaw(name)
+
+    def getHeader(self, name, default=None):
+        """Ret9urns the parsed representation of the given header.
+        The exact form of the return value depends on the header in question.
+
+        If no parser for the header exists, raise ValueError.
+
+        If the header doesn't exist, return default (or None if not specified)
+        """
+        name=name.lower()
+        parsed = self._headers.get(name, default)
+        if parsed is not _RecalcNeeded:
+            return parsed
+        return self._toParsed(name)
+
+    def setRawHeaders(self, name, value):
+        """Sets the raw representation of the given header.
+        Value should be a list of strings, each being one header of the
+        given name.
+        """
+        name=name.lower()
+        self._raw_headers[name] = value
+        self._headers[name] = _RecalcNeeded
+
+    def setHeader(self, name, value):
+        """Sets the parsed representation of the given header.
+        Value should be a list of objects whose exact form depends
+        on the header in question.
+        """
+        name=name.lower()
+        self._raw_headers[name] = _RecalcNeeded
+        self._headers[name] = value
+
+    def addRawHeader(self, name, value):
+        """
+        Add a raw value to a header that may or may not already exist.
+        If it exists, add it as a separate header to output; do not
+        replace anything.
+        """
+        name=name.lower()
+        raw_header = self._raw_headers.get(name)
+        if raw_header is None:
+            # No header yet
+            raw_header = []
+            self._raw_headers[name] = raw_header
+        elif raw_header is _RecalcNeeded:
+            raw_header = self._toRaw(name)
+
+        raw_header.append(value)
+        self._headers[name] = _RecalcNeeded
+
+    def removeHeader(self, name):
+        """Removes the header named."""
+
+        name=name.lower()
+        if self._raw_headers.has_key(name):
+            del self._raw_headers[name]
+            del self._headers[name]
+
+    def __repr__(self):
+        return '<Headers: Raw: %s Parsed: %s>'% (self._raw_headers, self._headers)
+
+    def canonicalNameCaps(self, name):
+        """Return the name with the canonical capitalization, if known,
+        otherwise, Caps-After-Dashes"""
+        return header_case_mapping.get(name) or dashCapitalize(name)
+
+    def getAllRawHeaders(self):
+        """Return an iterator of key,value pairs of all headers
+        contained in this object, as strings. The keys are capitalized
+        in canonical capitalization."""
+        for k,v in self._raw_headers.iteritems():
+            if v is _RecalcNeeded:
+                v = self._toRaw(k)
+            yield self.canonicalNameCaps(k), v
+
+    def makeImmutable(self):
+        """Make this header set immutable. All mutating operations will
+        raise an exception."""
+        self.setHeader = self.setRawHeaders = self.removeHeader = self._mutateRaise
+
+    def _mutateRaise(self, *args):
+        raise AttributeError("This header object is immutable as the headers have already been sent.")
+
+
+"""The following dicts are all mappings of header to list of operations
+   to perform. The first operation should generally be 'tokenize' if the
+   header can be parsed according to the normal tokenization rules. If
+   it cannot, generally the first thing you want to do is take only the
+   last instance of the header (in case it was sent multiple times, which
+   is strictly an error, but we're nice.).
+   """
+
+iteritems = lambda x: x.iteritems()
+
+
+parser_general_headers = {
+    'Cache-Control':(tokenize, listParser(parseCacheControl), dict),
+    'Connection':(tokenize,filterTokens),
+    'Date':(last,parseDateTime),
+#    'Pragma':tokenize
+#    'Trailer':tokenize
+    'Transfer-Encoding':(tokenize,filterTokens),
+#    'Upgrade':tokenize
+#    'Via':tokenize,stripComment
+#    'Warning':tokenize
+}
+
+generator_general_headers = {
+    'Cache-Control':(iteritems, listGenerator(generateCacheControl), singleHeader),
+    'Connection':(generateList,singleHeader),
+    'Date':(generateDateTime,singleHeader),
+#    'Pragma':
+#    'Trailer':
+    'Transfer-Encoding':(generateList,singleHeader),
+#    'Upgrade':
+#    'Via':
+#    'Warning':
+}
+
+parser_request_headers = {
+    'Accept': (tokenize, listParser(parseAccept), dict),
+    'Accept-Charset': (tokenize, listParser(parseAcceptQvalue), dict, addDefaultCharset),
+    'Accept-Encoding':(tokenize, listParser(parseAcceptQvalue), dict, addDefaultEncoding),
+    'Accept-Language':(tokenize, listParser(parseAcceptQvalue), dict),
+    'Authorization': (last, parseAuthorization),
+    'Cookie':(parseCookie,),
+    'Expect':(tokenize, listParser(parseExpect), dict),
+    'From':(last,),
+    'Host':(last,),
+    'If-Match':(tokenize, listParser(parseStarOrETag), list),
+    'If-Modified-Since':(last, parseIfModifiedSince),
+    'If-None-Match':(tokenize, listParser(parseStarOrETag), list),
+    'If-Range':(parseIfRange,),
+    'If-Unmodified-Since':(last,parseDateTime),
+    'Max-Forwards':(last,int),
+#    'Proxy-Authorization':str, # what is "credentials"
+    'Range':(tokenize, parseRange),
+    'Referer':(last,str), # TODO: URI object?
+    'TE':(tokenize, listParser(parseAcceptQvalue), dict),
+    'User-Agent':(last,str),
+}
+
+generator_request_headers = {
+    'Accept': (iteritems,listGenerator(generateAccept),singleHeader),
+    'Accept-Charset': (iteritems, listGenerator(generateAcceptQvalue),singleHeader),
+    'Accept-Encoding': (iteritems, removeDefaultEncoding, listGenerator(generateAcceptQvalue),singleHeader),
+    'Accept-Language': (iteritems, listGenerator(generateAcceptQvalue),singleHeader),
+    'Authorization': (generateAuthorization,), # what is "credentials"
+    'Cookie':(generateCookie,singleHeader),
+    'Expect':(iteritems, listGenerator(generateExpect), singleHeader),
+    'From':(str,singleHeader),
+    'Host':(str,singleHeader),
+    'If-Match':(listGenerator(generateStarOrETag), singleHeader),
+    'If-Modified-Since':(generateDateTime,singleHeader),
+    'If-None-Match':(listGenerator(generateStarOrETag), singleHeader),
+    'If-Range':(generateIfRange, singleHeader),
+    'If-Unmodified-Since':(generateDateTime,singleHeader),
+    'Max-Forwards':(str, singleHeader),
+#    'Proxy-Authorization':str, # what is "credentials"
+    'Range':(generateRange,singleHeader),
+    'Referer':(str,singleHeader),
+    'TE': (iteritems, listGenerator(generateAcceptQvalue),singleHeader),
+    'User-Agent':(str,singleHeader),
+}
+
+parser_response_headers = {
+    'Accept-Ranges':(tokenize, filterTokens),
+    'Age':(last,int),
+    'ETag':(tokenize, ETag.parse),
+    'Location':(last,), # TODO: URI object?
+#    'Proxy-Authenticate'
+    'Retry-After':(last, parseRetryAfter),
+    'Server':(last,),
+    'Set-Cookie':(parseSetCookie,),
+    'Set-Cookie2':(tokenize, parseSetCookie2),
+    'Vary':(tokenize, filterTokens),
+    'WWW-Authenticate': (lambda h: tokenize(h, foldCase=False),
+                         parseWWWAuthenticate,)
+}
+
+generator_response_headers = {
+    'Accept-Ranges':(generateList, singleHeader),
+    'Age':(str, singleHeader),
+    'ETag':(ETag.generate, singleHeader),
+    'Location':(str, singleHeader),
+#    'Proxy-Authenticate'
+    'Retry-After':(generateRetryAfter, singleHeader),
+    'Server':(str, singleHeader),
+    'Set-Cookie':(generateSetCookie,),
+    'Set-Cookie2':(generateSetCookie2,),
+    'Vary':(generateList, singleHeader),
+    'WWW-Authenticate':(generateWWWAuthenticate,)
+}
+
+parser_entity_headers = {
+    'Allow':(lambda str:tokenize(str, foldCase=False), filterTokens),
+    'Content-Encoding':(tokenize, filterTokens),
+    'Content-Language':(tokenize, filterTokens),
+    'Content-Length':(last, int),
+    'Content-Location':(last,), # TODO: URI object?
+    'Content-MD5':(last, parseContentMD5),
+    'Content-Range':(last, parseContentRange),
+    'Content-Type':(lambda str:tokenize(str, foldCase=False), parseContentType),
+    'Expires':(last, parseExpires),
+    'Last-Modified':(last, parseDateTime),
+    }
+
+generator_entity_headers = {
+    'Allow':(generateList, singleHeader),
+    'Content-Encoding':(generateList, singleHeader),
+    'Content-Language':(generateList, singleHeader),
+    'Content-Length':(str, singleHeader),
+    'Content-Location':(str, singleHeader),
+    'Content-MD5':(base64.encodestring, lambda x: x.strip("\n"), singleHeader),
+    'Content-Range':(generateContentRange, singleHeader),
+    'Content-Type':(generateContentType, singleHeader),
+    'Expires':(generateDateTime, singleHeader),
+    'Last-Modified':(generateDateTime, singleHeader),
+    }
+
+parser_dav_headers = {
+    'DAV'         : (tokenize, list),
+    'Depth'       : (last, parseDepth),
+    'Destination' : (last,), # TODO: URI object?
+   #'If'          : (),
+   #'Lock-Token'  : (),
+    'Overwrite'   : (last, parseOverWrite),
+   #'Status-URI'  : (),
+   #'Timeout'     : (),
+}
+
+generator_dav_headers = {
+    'DAV'         : (generateList, singleHeader),
+    'Depth'       : (singleHeader),
+    'Destination' : (singleHeader),
+   #'If'          : (),
+   #'Lock-Token'  : (),
+    'Overwrite'   : (),
+   #'Status-URI'  : (),
+   #'Timeout'     : (),
+}
+
+DefaultHTTPHandler.updateParsers(parser_general_headers)
+DefaultHTTPHandler.updateParsers(parser_request_headers)
+DefaultHTTPHandler.updateParsers(parser_response_headers)
+DefaultHTTPHandler.updateParsers(parser_entity_headers)
+DefaultHTTPHandler.updateParsers(parser_dav_headers)
+
+DefaultHTTPHandler.updateGenerators(generator_general_headers)
+DefaultHTTPHandler.updateGenerators(generator_request_headers)
+DefaultHTTPHandler.updateGenerators(generator_response_headers)
+DefaultHTTPHandler.updateGenerators(generator_entity_headers)
+DefaultHTTPHandler.updateGenerators(generator_dav_headers)
+
+
+# casemappingify(DefaultHTTPParsers)
+# casemappingify(DefaultHTTPGenerators)
+
+# lowerify(DefaultHTTPParsers)
+# lowerify(DefaultHTTPGenerators)

Copied: CalendarServer/trunk/twext/web2/iweb.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/iweb.py)
===================================================================
--- CalendarServer/trunk/twext/web2/iweb.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/iweb.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,276 @@
+# -*- test-case-name: twext.web2.test -*-
+##
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.
+#
+##
+
+"""
+I contain the interfaces for several web related objects including IRequest
+and IResource.  I am based heavily on ideas from C{nevow.inevow}.
+"""
+
+from zope.interface import Attribute, Interface, interface
+
+# server.py interfaces
+class IResource(Interface):
+    """
+    An HTTP resource.
+
+    I serve 2 main purposes: one is to provide a standard representation for
+    what HTTP specification calls an 'entity', and the other is to provide an
+    mechanism for mapping URLs to content.
+    """
+
+    def locateChild(req, segments):
+        """
+        Locate another object which can be adapted to IResource.
+
+        @return: A 2-tuple of (resource, remaining-path-segments),
+                 or a deferred which will fire the above.
+                 
+                 Causes the object publishing machinery to continue on
+                 with specified resource and segments, calling the
+                 appropriate method on the specified resource.
+                 
+                 If you return (self, L{server.StopTraversal}), this
+                 instructs web2 to immediately stop the lookup stage,
+                 and switch to the rendering stage, leaving the
+                 remaining path alone for your render function to
+                 handle.
+        """
+
+    def renderHTTP(req):
+        """
+        Return an IResponse or a deferred which will fire an
+        IResponse. This response will be written to the web browser
+        which initiated the request.
+        """
+
+# Is there a better way to do this than this funky extra class?
+_default = object()
+class SpecialAdaptInterfaceClass(interface.InterfaceClass):
+    # A special adapter for IResource to handle the extra step of adapting
+    # from IOldNevowResource-providing resources.
+    def __call__(self, other, alternate=_default):
+        result = super(SpecialAdaptInterfaceClass, self).__call__(other, alternate)
+        if result is not alternate:
+            return result
+        
+        result = IOldNevowResource(other, alternate)
+        if result is not alternate:
+            result = IResource(result)
+            return result
+        if alternate is not _default:
+            return alternate
+        raise TypeError('Could not adapt', other, self)
+IResource.__class__ = SpecialAdaptInterfaceClass
+
+class IOldNevowResource(Interface):
+    # Shared interface with inevow.IResource
+    """
+    I am a web resource.
+    """
+
+    def locateChild(ctx, segments):
+        """
+        Locate another object which can be adapted to IResource
+        Return a tuple of resource, path segments
+        """
+
+    def renderHTTP(ctx):
+        """
+        Return a string or a deferred which will fire a string. This string
+        will be written to the web browser which initiated this request.
+
+        Unlike iweb.IResource, this expects the incoming data to have already been read
+        and parsed into request.args and request.content, and expects to return a
+        string instead of a response object.
+        """
+
+class ICanHandleException(Interface):
+    
+    # Shared interface with inevow.ICanHandleException
+    def renderHTTP_exception(request, failure):
+        """
+        Render an exception to the given request object.
+        """
+
+    def renderInlineException(request, reason):
+        """
+        Return stan representing the exception, to be printed in the page,
+        not replacing the page."""
+
+
+# http.py interfaces
+class IResponse(Interface):
+    """
+    I'm a response.
+    """
+    code = Attribute("The HTTP response code")
+    headers = Attribute("A http_headers.Headers instance of headers to send")
+    stream = Attribute("A stream.IByteStream of outgoing data, or else None.")
+
+class IRequest(Interface):
+    """
+    I'm a request for a web resource.
+    """
+
+    method = Attribute("The HTTP method from the request line, e.g. GET")
+    uri = Attribute("The raw URI from the request line. May or may not include host.")
+    clientproto = Attribute("Protocol from the request line, e.g. HTTP/1.1")
+    
+    headers = Attribute("A http_headers.Headers instance of incoming headers.")
+    stream = Attribute("A stream.IByteStream of incoming data.")
+    
+    def writeResponse(response):
+        """
+        Write an IResponse object to the client.
+        """
+        
+    chanRequest = Attribute("The ChannelRequest. I wonder if this is public really?")
+
+
+from twisted.web.iweb import IRequest as IOldRequest
+
+
+class IChanRequestCallbacks(Interface):
+    """
+    The bits that are required of a Request for interfacing with a
+    IChanRequest object
+    """
+
+    def __init__(chanRequest, command, path, version, contentLength, inHeaders):
+        """
+        Create a new Request object.
+        
+        @param chanRequest: the IChanRequest object creating this request
+        @param command: the HTTP command e.g. GET
+        @param path: the HTTP path e.g. /foo/bar.html
+        @param version: the parsed HTTP version e.g. (1,1)
+        @param contentLength: how much data to expect, or None if unknown
+        @param inHeaders: the request headers"""
+
+    def process():
+        """
+        Process the request. Called as soon as it's possibly reasonable
+        to return a response. L{handleContentComplete} may or may not
+        have been called already.
+        """
+        
+    def handleContentChunk(data):
+        """
+        Called when a piece of incoming data has been received.
+        """
+        
+    def handleContentComplete():
+        """
+        Called when the incoming data stream is finished.
+        """
+        
+    def connectionLost(reason):
+        """
+        Called if the connection was lost.
+        """
+        
+    
+class IChanRequest(Interface):
+    
+    def writeIntermediateResponse(code, headers=None):
+        """
+        Write a non-terminating response.
+        
+        Intermediate responses cannot contain data.
+        If the channel does not support intermediate responses, do nothing.
+        
+        @param code: The response code. Should be in the 1xx range.
+        @type code: int
+        @param headers: the headers to send in the response
+        @type headers: C{twisted.web.http_headers.Headers}
+        """
+    
+    def writeHeaders(code, headers):
+        """
+        Write a final response.
+
+        @param code: The response code. Should not be in the 1xx range.
+        @type code: int
+        @param headers: the headers to send in the response. They will
+            be augmented with any connection-oriented headers as
+            necessary for the protocol.
+        @type headers: C{twisted.web.http_headers.Headers}
+        """
+        
+    def write(data):
+        """
+        Write some data.
+
+        @param data: the data bytes
+        @type data: str
+        """
+    
+    def finish():
+        """
+        Finish the request, and clean up the connection if necessary.
+        """
+    
+    def abortConnection():
+        """
+        Forcibly abort the connection without cleanly closing.
+        
+        Use if, for example, you can't write all the data you promised.
+        """
+
+    def registerProducer(producer, streaming):
+        """
+        Register a producer with the standard API.
+        """
+    
+    def unregisterProducer():
+        """
+        Unregister a producer.
+        """
+
+    def getHostInfo():
+        """
+        Returns a tuple of (address, socket user connected to,
+        boolean, was it secure).  Note that this should not necessarily
+        always return the actual local socket information from
+        twisted. E.g. in a CGI, it should use the variables coming
+        from the invoking script.
+        """
+
+    def getRemoteHost():
+        """
+        Returns an address of the remote host.
+
+        Like L{getHostInfo}, this information may come from the real
+        socket, or may come from additional information, depending on
+        the transport.
+        """
+
+    persistent = Attribute("""Whether this request supports HTTP connection persistence. May be set to False. Should not be set to other values.""")
+
+
+class ISite(Interface):
+    pass
+
+__all__ = ['ICanHandleException', 'IChanRequest', 'IChanRequestCallbacks', 'IOldNevowResource', 'IOldRequest', 'IRequest', 'IResource', 'IResponse', 'ISite']

Copied: CalendarServer/trunk/twext/web2/log.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/log.py)
===================================================================
--- CalendarServer/trunk/twext/web2/log.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/log.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,222 @@
+# -*- test-case-name: twext.web2.test.test_log -*-
+##
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.
+#
+##
+
+"""Logging tools. This is still in flux (even moreso than the rest of web2)."""
+
+import time
+from twisted.python import log as tlog
+from twisted.internet import defer
+from twext.web2 import iweb, stream, resource
+from zope.interface import implements, Attribute, Interface
+
+class _LogByteCounter(object):
+    implements(stream.IByteStream)
+    
+    def __init__(self, stream, done):
+        self.stream=stream
+        self.done=done
+        self.len=0
+        
+    length=property(lambda self: self.stream.length)
+    
+    def _callback(self, data):
+        if data is None:
+            if self.done:
+                done=self.done; self.done=None
+                done(True, self.len)
+        else:
+            self.len += len(data)
+        return data
+    
+    def read(self):
+        data = self.stream.read()
+        if isinstance(data, defer.Deferred):
+            return data.addCallback(self._callback)
+        return self._callback(data)
+    
+    def close(self):
+        if self.done:
+            done=self.done; self.done=None
+            done(False, self.len)
+        self.stream.close()
+
+    
+class ILogInfo(Interface):
+    """Auxilliary information about the response useful for logging."""
+    
+    bytesSent=Attribute("Number of bytes sent.")
+    responseCompleted=Attribute("Whether or not the response was completed.")
+    secondsTaken=Attribute("Number of seconds taken to serve the request.")
+    startTime=Attribute("Time at which the request started")
+
+    
+class LogInfo(object):
+    implements(ILogInfo)
+
+    responseCompleted=None
+    secondsTaken=None
+    bytesSent=None
+    startTime=None
+
+    
+def logFilter(request, response, startTime=None):
+    if startTime is None:
+        startTime = time.time()
+        
+    def _log(success, length):
+        loginfo=LogInfo()
+        loginfo.bytesSent=length
+        loginfo.responseCompleted=success
+        loginfo.secondsTaken=time.time()-startTime
+        
+        tlog.msg(interface=iweb.IRequest, request=request, response=response,
+                 loginfo=loginfo)
+        # Or just...
+        # ILogger(ctx).log(...) ?
+
+    if response.stream:
+        response.stream=_LogByteCounter(response.stream, _log)
+    else:
+        _log(True, 0)
+
+    return response
+
+logFilter.handleErrors = True
+
+
+class LogWrapperResource(resource.WrapperResource):
+    def hook(self, request):
+        # Insert logger
+        request.addResponseFilter(logFilter, atEnd=True, onlyOnce=True)
+
+monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+             'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+
+
+class BaseCommonAccessLoggingObserver(object):
+    """An abstract Twisted-based logger for creating access logs.
+
+    Derived implementations of this class *must* implement the
+    ``logMessage(message)`` method, which will send the message to an actual
+    log/file or stream.
+    """
+
+    logFormat = '%s - %s [%s] "%s" %s %d "%s" "%s"'
+    def logMessage(self, message):
+        raise NotImplemented, 'You must provide an implementation.'
+
+    def computeTimezoneForLog(self, tz):
+        if tz > 0:
+            neg = 1
+        else:
+            neg = 0
+            tz = -tz
+        h, rem = divmod(tz, 3600)
+        m, rem = divmod(rem, 60)
+        if neg:
+            return '-%02d%02d' % (h, m)
+        else:
+            return '+%02d%02d' % (h, m)
+
+    tzForLog = None
+    tzForLogAlt = None
+
+    def logDateString(self, when):
+        logtime = time.localtime(when)
+        Y, M, D, h, m, s = logtime[:6]
+        
+        if not time.daylight:
+            tz = self.tzForLog
+            if tz is None:
+                tz = self.computeTimezoneForLog(time.timezone)
+                self.tzForLog = tz
+        else:
+            tz = self.tzForLogAlt
+            if tz is None:
+                tz = self.computeTimezoneForLog(time.altzone)
+                self.tzForLogAlt = tz
+
+        return '%02d/%s/%02d:%02d:%02d:%02d %s' % (
+            D, monthname[M], Y, h, m, s, tz)
+
+    def emit(self, eventDict):
+        if eventDict.get('interface') is not iweb.IRequest:
+            return
+
+        request = eventDict['request']
+        response = eventDict['response']
+        loginfo = eventDict['loginfo']
+        firstLine = '%s %s HTTP/%s' %(
+            request.method,
+            request.uri,
+            '.'.join([str(x) for x in request.clientproto]))
+        
+        self.logMessage(
+            '%s - %s [%s] "%s" %s %d "%s" "%s"' %(
+                request.remoteAddr.host,
+                # XXX: Where to get user from?
+                "-",
+                self.logDateString(
+                    response.headers.getHeader('date', 0)),
+                firstLine,
+                response.code,
+                loginfo.bytesSent,
+                request.headers.getHeader('referer', '-'),
+                request.headers.getHeader('user-agent', '-')
+                )
+            )
+
+    def start(self):
+        """Start observing log events."""
+        tlog.addObserver(self.emit)
+
+    def stop(self):
+        """Stop observing log events."""
+        tlog.removeObserver(self.emit)
+
+
+class FileAccessLoggingObserver(BaseCommonAccessLoggingObserver):
+    """I log requests to a single logfile
+    """
+    
+    def __init__(self, logpath):
+        self.logpath = logpath
+                
+    def logMessage(self, message):
+        self.f.write(message + '\n')
+
+    def start(self):
+        super(FileAccessLoggingObserver, self).start()
+        self.f = open(self.logpath, 'a', 1)
+        
+    def stop(self):
+        super(FileAccessLoggingObserver, self).stop()
+        self.f.close()
+
+                
+class DefaultCommonAccessLoggingObserver(BaseCommonAccessLoggingObserver):
+    """Log requests to default twisted logfile."""
+    def logMessage(self, message):
+        tlog.msg(message)

Copied: CalendarServer/trunk/twext/web2/resource.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/resource.py)
===================================================================
--- CalendarServer/trunk/twext/web2/resource.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/resource.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,316 @@
+# -*- test-case-name: twext.web2.test.test_server,twext.web2.test.test_resource -*-
+##
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.
+#
+##
+
+"""
+I hold the lowest-level L{Resource} class and related mix-in classes.
+"""
+
+# System Imports
+from zope.interface import implements
+
+from twext.web2 import iweb, http, server, responsecode
+
+class RenderMixin(object):
+    """
+    Mix-in class for L{iweb.IResource} which provides a dispatch mechanism for
+    handling HTTP methods.
+    """
+    def allowedMethods(self):
+        """
+        @return: A tuple of HTTP methods that are allowed to be invoked on this resource.
+        """
+        if not hasattr(self, "_allowed_methods"):
+            self._allowed_methods = tuple([name[5:] for name in dir(self) if name.startswith('http_')])
+        return self._allowed_methods
+
+    def checkPreconditions(self, request):
+        """
+        Checks all preconditions imposed by this resource upon a request made
+        against it.
+        @param request: the request to process.
+        @raise http.HTTPError: if any precondition fails.
+        @return: C{None} or a deferred whose callback value is C{request}.
+        """
+        #
+        # http.checkPreconditions() gets called by the server after every
+        # GET or HEAD request.
+        #
+        # For other methods, we need to know to bail out before request
+        # processing, especially for methods that modify server state (eg. PUT).
+        # We also would like to do so even for methods that don't, if those
+        # methods might be expensive to process.  We're assuming that GET and
+        # HEAD are not expensive.
+        #
+        if request.method not in ("GET", "HEAD"):
+            http.checkPreconditions(request)
+
+        # Check per-method preconditions
+        method = getattr(self, "preconditions_" + request.method, None)
+        if method:
+            return method(request)
+
+    def renderHTTP(self, request):
+        """
+        See L{iweb.IResource.renderHTTP}.
+
+        This implementation will dispatch the given C{request} to another method
+        of C{self} named C{http_}METHOD, where METHOD is the HTTP method used by
+        C{request} (eg. C{http_GET}, C{http_POST}, etc.).
+
+        Generally, a subclass should implement those methods instead of
+        overriding this one.
+
+        C{http_*} methods are expected provide the same interface and return the
+        same results as L{iweb.IResource}C{.renderHTTP} (and therefore this method).
+
+        C{etag} and C{last-modified} are added to the response returned by the
+        C{http_*} header, if known.
+
+        If an appropriate C{http_*} method is not found, a
+        L{responsecode.NOT_ALLOWED}-status response is returned, with an
+        appropriate C{allow} header.
+
+        @param request: the request to process.
+        @return: an object adaptable to L{iweb.IResponse}.
+        """
+        method = getattr(self, "http_" + request.method, None)
+        if not method:
+            response = http.Response(responsecode.NOT_ALLOWED)
+            response.headers.setHeader("allow", self.allowedMethods())
+            return response
+
+        d = self.checkPreconditions(request)
+        if d is None:
+            return method(request)
+        else:
+            return d.addCallback(lambda _: method(request))
+
+    def http_OPTIONS(self, request):
+        """
+        Respond to a OPTIONS request.
+        @param request: the request to process.
+        @return: an object adaptable to L{iweb.IResponse}.
+        """
+        response = http.Response(responsecode.OK)
+        response.headers.setHeader("allow", self.allowedMethods())
+        return response
+
+    def http_TRACE(self, request):
+        """
+        Respond to a TRACE request.
+        @param request: the request to process.
+        @return: an object adaptable to L{iweb.IResponse}.
+        """
+        return server.doTrace(request)
+
+    def http_HEAD(self, request):
+        """
+        Respond to a HEAD request.
+        @param request: the request to process.
+        @return: an object adaptable to L{iweb.IResponse}.
+        """
+        return self.http_GET(request)
+
+    def http_GET(self, request):
+        """
+        Respond to a GET request.
+
+        This implementation validates that the request body is empty and then
+        dispatches the given C{request} to L{render} and returns its result.
+
+        @param request: the request to process.
+        @return: an object adaptable to L{iweb.IResponse}.
+        """
+        if request.stream.length != 0:
+            return responsecode.REQUEST_ENTITY_TOO_LARGE
+
+        return self.render(request)
+
+    def render(self, request):
+        """
+        Subclasses should implement this method to do page rendering.
+        See L{http_GET}.
+        @param request: the request to process.
+        @return: an object adaptable to L{iweb.IResponse}.
+        """
+        raise NotImplementedError("Subclass must implement render method.")
+
+class Resource(RenderMixin):
+    """
+    An L{iweb.IResource} implementation with some convenient mechanisms for
+    locating children.
+    """
+    implements(iweb.IResource)
+
+    addSlash = False
+
+    def locateChild(self, request, segments):
+        """
+        Locates a child resource of this resource.
+        @param request: the request to process.
+        @param segments: a sequence of URL path segments.
+        @return: a tuple of C{(child, segments)} containing the child
+        of this resource which matches one or more of the given C{segments} in
+        sequence, and a list of remaining segments.
+        """
+        w = getattr(self, 'child_%s' % (segments[0], ), None)
+
+        if w:
+            r = iweb.IResource(w, None)
+            if r:
+                return r, segments[1:]
+            return w(request), segments[1:]
+
+        factory = getattr(self, 'childFactory', None)
+        if factory is not None:
+            r = factory(request, segments[0])
+            if r:
+                return r, segments[1:]
+
+        return None, []
+
+    def child_(self, request):
+        """
+        This method locates a child with a trailing C{"/"} in the URL.
+        @param request: the request to process.
+        """
+        if self.addSlash and len(request.postpath) == 1:
+            return self
+        return None
+
+    def putChild(self, path, child):
+        """
+        Register a static child.
+
+        This implementation registers children by assigning them to attributes
+        with a C{child_} prefix.  C{resource.putChild("foo", child)} is
+        therefore same as C{o.child_foo = child}.
+
+        @param path: the name of the child to register.  You almost certainly
+            don't want C{"/"} in C{path}. If you want to add a "directory"
+            resource (e.g. C{/foo/}) specify C{path} as C{""}.
+        @param child: an object adaptable to L{iweb.IResource}.
+        """
+        setattr(self, 'child_%s' % (path, ), child)
+
+    def http_GET(self, request):
+        if self.addSlash and request.prepath[-1] != '':
+            # If this is a directory-ish resource...
+            return http.RedirectResponse(request.unparseURL(path=request.path+'/'))
+
+        return super(Resource, self).http_GET(request)
+
+
+class PostableResource(Resource):
+    """
+    A L{Resource} capable of handling the POST request method.
+
+    @cvar maxMem: maximum memory used during the parsing of the data.
+    @type maxMem: C{int}
+    @cvar maxFields: maximum number of form fields allowed.
+    @type maxFields: C{int}
+    @cvar maxSize: maximum size of the whole post allowed.
+    @type maxSize: C{int}
+    """
+    maxMem = 100 * 1024
+    maxFields = 1024
+    maxSize = 10 * 1024 * 1024
+
+    def http_POST(self, request):
+        """
+        Respond to a POST request.
+        Reads and parses the incoming body data then calls L{render}.
+
+        @param request: the request to process.
+        @return: an object adaptable to L{iweb.IResponse}.
+        """
+        return server.parsePOSTData(request,
+            self.maxMem, self.maxFields, self.maxSize
+            ).addCallback(lambda res: self.render(request))
+
+
+class LeafResource(RenderMixin):
+    """
+    A L{Resource} with no children.
+    """
+    implements(iweb.IResource)
+
+    def locateChild(self, request, segments):
+        return self, server.StopTraversal
+
+class RedirectResource(LeafResource):
+    """
+    A L{LeafResource} which always performs a redirect.
+    """
+    implements(iweb.IResource)
+
+    def __init__(self, *args, **kwargs):
+        """
+        Parameters are URL components and are the same as those for
+        L{urlparse.urlunparse}.  URL components which are not specified will
+        default to the corresponding component of the URL of the request being
+        redirected.
+        """
+        self._args   = args
+        self._kwargs = kwargs
+
+    def renderHTTP(self, request):
+        return http.RedirectResponse(request.unparseURL(*self._args, **self._kwargs))
+
+class WrapperResource(object):
+    """
+    An L{iweb.IResource} implementation which wraps a L{RenderMixin} instance
+    and provides a hook in which a subclass can implement logic that is called
+    before request processing on the contained L{Resource}.
+    """
+    implements(iweb.IResource)
+
+    def __init__(self, resource):
+        self.resource=resource
+
+    def hook(self, request):
+        """
+        Override this method in order to do something before passing control on
+        to the wrapped resource's C{renderHTTP} and C{locateChild} methods.
+        @return: None or a L{Deferred}.  If a deferred object is
+            returned, it's value is ignored, but C{renderHTTP} and
+            C{locateChild} are chained onto the deferred as callbacks.
+        """
+        raise NotImplementedError()
+
+    def locateChild(self, request, segments):
+        x = self.hook(request)
+        if x is not None:
+            return x.addCallback(lambda data: (self.resource, segments))
+        return self.resource, segments
+
+    def renderHTTP(self, request):
+        x = self.hook(request)
+        if x is not None:
+            return x.addCallback(lambda data: self.resource)
+        return self.resource
+
+
+__all__ = ['RenderMixin', 'Resource', 'PostableResource', 'LeafResource', 'WrapperResource']

Copied: CalendarServer/trunk/twext/web2/responsecode.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/responsecode.py)
===================================================================
--- CalendarServer/trunk/twext/web2/responsecode.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/responsecode.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,136 @@
+# -*- test-case-name: twext.web2.test -*-
+##
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.
+#
+##
+
+CONTINUE                        = 100
+SWITCHING                       = 101
+
+OK                              = 200
+CREATED                         = 201
+ACCEPTED                        = 202
+NON_AUTHORITATIVE_INFORMATION   = 203
+NO_CONTENT                      = 204
+RESET_CONTENT                   = 205
+PARTIAL_CONTENT                 = 206
+MULTI_STATUS                    = 207
+
+MULTIPLE_CHOICE                 = 300
+MOVED_PERMANENTLY               = 301
+FOUND                           = 302
+SEE_OTHER                       = 303
+NOT_MODIFIED                    = 304
+USE_PROXY                       = 305
+TEMPORARY_REDIRECT              = 307
+
+BAD_REQUEST                     = 400
+UNAUTHORIZED                    = 401
+PAYMENT_REQUIRED                = 402
+FORBIDDEN                       = 403
+NOT_FOUND                       = 404
+NOT_ALLOWED                     = 405
+NOT_ACCEPTABLE                  = 406
+PROXY_AUTH_REQUIRED             = 407
+REQUEST_TIMEOUT                 = 408
+CONFLICT                        = 409
+GONE                            = 410
+LENGTH_REQUIRED                 = 411
+PRECONDITION_FAILED             = 412
+REQUEST_ENTITY_TOO_LARGE        = 413
+REQUEST_URI_TOO_LONG            = 414
+UNSUPPORTED_MEDIA_TYPE          = 415
+REQUESTED_RANGE_NOT_SATISFIABLE = 416
+EXPECTATION_FAILED              = 417
+UNPROCESSABLE_ENTITY            = 422 # RFC 2518
+LOCKED                          = 423 # RFC 2518
+FAILED_DEPENDENCY               = 424 # RFC 2518
+
+INTERNAL_SERVER_ERROR           = 500
+NOT_IMPLEMENTED                 = 501
+BAD_GATEWAY                     = 502
+SERVICE_UNAVAILABLE             = 503
+GATEWAY_TIMEOUT                 = 504
+HTTP_VERSION_NOT_SUPPORTED      = 505
+INSUFFICIENT_STORAGE_SPACE      = 507
+NOT_EXTENDED                    = 510
+
+RESPONSES = {
+    # 100
+    CONTINUE: "Continue",
+    SWITCHING: "Switching Protocols",
+
+    # 200
+    OK: "OK",
+    CREATED: "Created",
+    ACCEPTED: "Accepted",
+    NON_AUTHORITATIVE_INFORMATION: "Non-Authoritative Information",
+    NO_CONTENT: "No Content",
+    RESET_CONTENT: "Reset Content.",
+    PARTIAL_CONTENT: "Partial Content",
+    MULTI_STATUS: "Multi-Status",
+
+    # 300
+    MULTIPLE_CHOICE: "Multiple Choices",
+    MOVED_PERMANENTLY: "Moved Permanently",
+    FOUND: "Found",
+    SEE_OTHER: "See Other",
+    NOT_MODIFIED: "Not Modified",
+    USE_PROXY: "Use Proxy",
+    # 306 unused
+    TEMPORARY_REDIRECT: "Temporary Redirect",
+
+    # 400
+    BAD_REQUEST: "Bad Request",
+    UNAUTHORIZED: "Unauthorized",
+    PAYMENT_REQUIRED: "Payment Required",
+    FORBIDDEN: "Forbidden",
+    NOT_FOUND: "Not Found",
+    NOT_ALLOWED: "Method Not Allowed",
+    NOT_ACCEPTABLE: "Not Acceptable",
+    PROXY_AUTH_REQUIRED: "Proxy Authentication Required",
+    REQUEST_TIMEOUT: "Request Time-out",
+    CONFLICT: "Conflict",
+    GONE: "Gone",
+    LENGTH_REQUIRED: "Length Required",
+    PRECONDITION_FAILED: "Precondition Failed",
+    REQUEST_ENTITY_TOO_LARGE: "Request Entity Too Large",
+    REQUEST_URI_TOO_LONG: "Request-URI Too Long",
+    UNSUPPORTED_MEDIA_TYPE: "Unsupported Media Type",
+    REQUESTED_RANGE_NOT_SATISFIABLE: "Requested Range not satisfiable",
+    EXPECTATION_FAILED: "Expectation Failed",
+    UNPROCESSABLE_ENTITY: "Unprocessable Entity",
+    LOCKED: "Locked",
+    FAILED_DEPENDENCY: "Failed Dependency",
+
+    # 500
+    INTERNAL_SERVER_ERROR: "Internal Server Error",
+    NOT_IMPLEMENTED: "Not Implemented",
+    BAD_GATEWAY: "Bad Gateway",
+    SERVICE_UNAVAILABLE: "Service Unavailable",
+    GATEWAY_TIMEOUT: "Gateway Time-out",
+    HTTP_VERSION_NOT_SUPPORTED: "HTTP Version not supported",
+    INSUFFICIENT_STORAGE_SPACE: "Insufficient Storage Space",
+    NOT_EXTENDED: "Not Extended"
+    }
+
+# No __all__ necessary -- everything is exported

Copied: CalendarServer/trunk/twext/web2/server.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/server.py)
===================================================================
--- CalendarServer/trunk/twext/web2/server.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/server.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,660 @@
+# -*- test-case-name: twext.web2.test.test_server -*-
+##
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.
+#
+##
+
+"""
+This is a web-server which integrates with the twisted.internet
+infrastructure.
+"""
+
+# System Imports
+import cgi, time, urlparse
+from urllib import quote, unquote
+from urlparse import urlsplit
+
+import weakref
+
+from zope.interface import implements
+# Twisted Imports
+from twisted.internet import defer
+from twisted.python import log, failure
+
+# Sibling Imports
+from twext.web2 import http, iweb, fileupload, responsecode
+from twext.web2 import http_headers
+from twext.web2.filter.range import rangefilter
+from twext.web2 import error
+from twext.web2.dav.util import joinURL
+
+from twext.web2 import __version__ as web2_version
+from twisted import __version__ as twisted_version
+
+VERSION = "Twisted/%s TwistedWeb/%s" % (twisted_version, web2_version)
+_errorMarker = object()
+
+
+def defaultHeadersFilter(request, response):
+    if not response.headers.hasHeader('server'):
+        response.headers.setHeader('server', VERSION)
+    if not response.headers.hasHeader('date'):
+        response.headers.setHeader('date', time.time())
+    return response
+defaultHeadersFilter.handleErrors = True
+
+def preconditionfilter(request, response):
+    if request.method in ("GET", "HEAD"):
+        http.checkPreconditions(request, response)
+    return response
+
+def doTrace(request):
+    request = iweb.IRequest(request)
+    txt = "%s %s HTTP/%d.%d\r\n" % (request.method, request.uri,
+                                    request.clientproto[0], request.clientproto[1])
+
+    l=[]
+    for name, valuelist in request.headers.getAllRawHeaders():
+        for value in valuelist:
+            l.append("%s: %s\r\n" % (name, value))
+    txt += ''.join(l)
+
+    return http.Response(
+        responsecode.OK,
+        {'content-type': http_headers.MimeType('message', 'http')},
+        txt)
+
+
+def parsePOSTData(request, maxMem=100*1024, maxFields=1024,
+                  maxSize=10*1024*1024):
+    """
+    Parse data of a POST request.
+
+    @param request: the request to parse.
+    @type request: L{twext.web2.http.Request}.
+    @param maxMem: maximum memory used during the parsing of the data.
+    @type maxMem: C{int}
+    @param maxFields: maximum number of form fields allowed.
+    @type maxFields: C{int}
+    @param maxSize: maximum size of file upload allowed.
+    @type maxSize: C{int}
+
+    @return: a deferred that will fire when the parsing is done. The deferred
+        itself doesn't hold a return value, the request is modified directly.
+    @rtype: C{defer.Deferred}
+    """
+    if request.stream.length == 0:
+        return defer.succeed(None)
+
+    parser = None
+    ctype = request.headers.getHeader('content-type')
+
+    if ctype is None:
+        return defer.succeed(None)
+
+    def updateArgs(data):
+        args = data
+        request.args.update(args)
+
+    def updateArgsAndFiles(data):
+        args, files = data
+        request.args.update(args)
+        request.files.update(files)
+
+    def error(f):
+        f.trap(fileupload.MimeFormatError)
+        raise http.HTTPError(
+            http.StatusResponse(responsecode.BAD_REQUEST, str(f.value)))
+
+    if (ctype.mediaType == 'application'
+        and ctype.mediaSubtype == 'x-www-form-urlencoded'):
+        d = fileupload.parse_urlencoded(request.stream)
+        d.addCallbacks(updateArgs, error)
+        return d
+    elif (ctype.mediaType == 'multipart'
+          and ctype.mediaSubtype == 'form-data'):
+        boundary = ctype.params.get('boundary')
+        if boundary is None:
+            return defer.fail(http.HTTPError(
+                    http.StatusResponse(
+                        responsecode.BAD_REQUEST,
+                        "Boundary not specified in Content-Type.")))
+        d = fileupload.parseMultipartFormData(request.stream, boundary,
+                                              maxMem, maxFields, maxSize)
+        d.addCallbacks(updateArgsAndFiles, error)
+        return d
+    else:
+        return defer.fail(http.HTTPError(
+            http.StatusResponse(
+                responsecode.BAD_REQUEST,
+                "Invalid content-type: %s/%s" % (
+                    ctype.mediaType, ctype.mediaSubtype))))
+
+
+class StopTraversal(object):
+    """
+    Indicates to Request._handleSegment that it should stop handling
+    path segments.
+    """
+    pass
+
+
+class Request(http.Request):
+    """
+    vars:
+    site
+
+    remoteAddr
+
+    scheme
+    host
+    port
+    path
+    params
+    querystring
+
+    args
+    files
+
+    prepath
+    postpath
+
+    @ivar path: The path only (arguments not included).
+    @ivar args: All of the arguments, including URL and POST arguments.
+    @type args: A mapping of strings (the argument names) to lists of values.
+                i.e., ?foo=bar&foo=baz&quux=spam results in
+                {'foo': ['bar', 'baz'], 'quux': ['spam']}.
+
+    """
+    implements(iweb.IRequest)
+
+    site = None
+    _initialprepath = None
+    responseFilters = [rangefilter, preconditionfilter,
+                       error.defaultErrorHandler, defaultHeadersFilter]
+
+    def __init__(self, *args, **kw):
+        
+        self.initTime = time.time()
+
+        if kw.has_key('site'):
+            self.site = kw['site']
+            del kw['site']
+        if kw.has_key('prepathuri'):
+            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, 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(filter)
+        else:
+            self.responseFilters.insert(0, filter)
+
+    def unparseURL(self, scheme=None, host=None, port=None,
+                   path=None, params=None, querystring=None, fragment=None):
+        """Turn the request path into a url string. For any pieces of
+        the url that are not specified, use the value from the
+        request. The arguments have the same meaning as the same named
+        attributes of Request."""
+
+        if scheme is None: scheme = self.scheme
+        if host is None: host = self.host
+        if port is None: port = self.port
+        if path is None: path = self.path
+        if params is None: params = self.params
+        if querystring is None: query = self.querystring
+        if fragment is None: fragment = ''
+
+        if port == http.defaultPortForScheme.get(scheme, 0):
+            hostport = host
+        else:
+            hostport = host + ':' + str(port)
+
+        return urlparse.urlunparse((
+            scheme, hostport, path,
+            params, querystring, fragment))
+
+    def _parseURL(self):
+        if self.uri[0] == '/':
+            # Can't use urlparse for request_uri because urlparse
+            # wants to be given an absolute or relative URI, not just
+            # an abs_path, and thus gets '//foo' wrong.
+            self.scheme = self.host = self.path = self.params = self.querystring = ''
+            if '?' in self.uri:
+                self.path, self.querystring = self.uri.split('?', 1)
+            else:
+                self.path = self.uri
+            if ';' in self.path:
+                self.path, self.params = self.path.split(';', 1)
+        else:
+            # It is an absolute uri, use standard urlparse
+            (self.scheme, self.host, self.path,
+             self.params, self.querystring, fragment) = urlparse.urlparse(self.uri)
+
+        if self.querystring:
+            self.args = cgi.parse_qs(self.querystring, True)
+        else:
+            self.args = {}
+
+        path = map(unquote, self.path[1:].split('/'))
+        if self._initialprepath:
+            # We were given an initial prepath -- this is for supporting
+            # CGI-ish applications where part of the path has already
+            # been processed
+            prepath = map(unquote, self._initialprepath[1:].split('/'))
+
+            if path[:len(prepath)] == prepath:
+                self.prepath = prepath
+                self.postpath = path[len(prepath):]
+            else:
+                self.prepath = []
+                self.postpath = path
+        else:
+            self.prepath = []
+            self.postpath = path
+        #print "_parseURL", self.uri, (self.uri, self.scheme, self.host, self.path, self.params, self.querystring)
+
+    def _fixupURLParts(self):
+        hostaddr, secure = self.chanRequest.getHostInfo()
+        if not self.scheme:
+            self.scheme = ('http', 'https')[secure]
+
+        if self.host:
+            self.host, self.port = http.splitHostPort(self.scheme, self.host)
+        else:
+            # If GET line wasn't an absolute URL
+            host = self.headers.getHeader('host')
+            if host:
+                self.host, self.port = http.splitHostPort(self.scheme, host)
+            else:
+                # When no hostname specified anywhere, either raise an
+                # error, or use the interface hostname, depending on
+                # protocol version
+                if self.clientproto >= (1,1):
+                    raise http.HTTPError(responsecode.BAD_REQUEST)
+                self.host = hostaddr.host
+                self.port = hostaddr.port
+
+
+    def process(self):
+        "Process a request."
+        try:
+            self.checkExpect()
+            resp = self.preprocessRequest()
+            if resp is not None:
+                self._cbFinishRender(resp).addErrback(self._processingFailed)
+                return
+            self._parseURL()
+            self._fixupURLParts()
+            self.remoteAddr = self.chanRequest.getRemoteHost()
+        except:
+            failedDeferred = self._processingFailed(failure.Failure())
+            return
+
+        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)
+        d.callback(None)
+
+    def preprocessRequest(self):
+        """Do any request processing that doesn't follow the normal
+        resource lookup procedure. "OPTIONS *" is handled here, for
+        example. This would also be the place to do any CONNECT
+        processing."""
+
+        if self.method == "OPTIONS" and self.uri == "*":
+            response = http.Response(responsecode.OK)
+            response.headers.setHeader('allow', ('GET', 'HEAD', 'OPTIONS', 'TRACE'))
+            return response
+
+        elif self.method == "POST":
+            # Allow other methods to tunnel through using POST and a request header.
+            # See http://code.google.com/apis/gdata/docs/2.0/basics.html
+            if self.headers.hasHeader("X-HTTP-Method-Override"):
+                intendedMethod = self.headers.getRawHeaders("X-HTTP-Method-Override")[0];
+                if intendedMethod:
+                    self.originalMethod = self.method
+                    self.method = intendedMethod
+
+        # This is where CONNECT would go if we wanted it
+        return None
+
+    def _getChild(self, _, res, path, updatepaths=True):
+        """Call res.locateChild, and pass the result on to _handleSegment."""
+
+        self.resources.append(res)
+
+        if not path:
+            return res
+
+        result = res.locateChild(self, path)
+        if isinstance(result, defer.Deferred):
+            return result.addCallback(self._handleSegment, res, path, updatepaths)
+        else:
+            return self._handleSegment(result, res, path, updatepaths)
+
+    def _handleSegment(self, result, res, path, updatepaths):
+        """Handle the result of a locateChild call done in _getChild."""
+
+        newres, newpath = result
+        # If the child resource is None then display a error page
+        if newres is None:
+            raise http.HTTPError(responsecode.NOT_FOUND)
+
+        # If we got a deferred then we need to call back later, once the
+        # child is actually available.
+        if isinstance(newres, defer.Deferred):
+            return newres.addCallback(
+                lambda actualRes: self._handleSegment(
+                    (actualRes, newpath), res, path, updatepaths)
+                )
+
+        if path:
+            url = quote("/" + "/".join(path))
+        else:
+            url = "/"
+
+        if newpath is StopTraversal:
+            # We need to rethink how to do this.
+            #if newres is res:
+                return res
+            #else:
+            #    raise ValueError("locateChild must not return StopTraversal with a resource other than self.")
+
+        newres = iweb.IResource(newres)
+        if newres is res:
+            assert not newpath is path, "URL traversal cycle detected when attempting to locateChild %r from resource %r." % (path, res)
+            assert len(newpath) < len(path), "Infinite loop impending..."
+
+        if updatepaths:
+            # We found a Resource... update the request.prepath and postpath
+            for x in xrange(len(path) - len(newpath)):
+                self.prepath.append(self.postpath.pop(0))
+            url = quote("/" + "/".join(self.prepath) + ("/" if self.prepath and self.prepath[-1] else ""))
+            self._rememberResource(newres, url)
+        else:
+            try:
+                previousURL = self.urlForResource(res)
+                url = quote(previousURL + path[0] + ("/" if path[0] and len(path) > 1 else ""))
+                self._rememberResource(newres, url)
+            except NoURLForResourceError:
+                pass
+
+        child = self._getChild(None, newres, newpath, updatepaths=updatepaths)
+
+        return child
+
+    _urlsByResource = weakref.WeakKeyDictionary()
+
+    def _rememberResource(self, resource, url):
+        """
+        Remember the URL of a visited resource.
+        """
+        self._resourcesByURL[url] = resource
+        self._urlsByResource[resource] = url
+        return resource
+
+    def urlForResource(self, resource):
+        """
+        Looks up the URL of the given resource if this resource was found while
+        processing this request.  Specifically, this includes the requested
+        resource, and resources looked up via L{locateResource}.
+
+        Note that a resource may be found at multiple URIs; if the same resource
+        is visited at more than one location while processing this request,
+        this method will return one of those URLs, but which one is not defined,
+        nor whether the same URL is returned in subsequent calls.
+
+        @param resource: the resource to find a URI for.  This resource must
+            have been obtained from the request (ie. via its C{uri} attribute, or
+            through its C{locateResource} or C{locateChildResource} methods).
+        @return: a valid URL for C{resource} in this request.
+        @raise NoURLForResourceError: if C{resource} has no URL in this request
+            (because it was not obtained from the request).
+        """
+        resource = self._urlsByResource.get(resource, None)
+        if resource is None:
+            raise NoURLForResourceError(resource)
+        return resource
+
+    def locateResource(self, url):
+        """
+        Looks up the resource with the given URL.
+        @param uri: The URL of the desired resource.
+        @return: a L{Deferred} resulting in the L{IResource} at the
+            given URL or C{None} if no such resource can be located.
+        @raise HTTPError: If C{url} is not a URL on the site that this
+            request is being applied to.  The contained response will
+            have a status code of L{responsecode.BAD_GATEWAY}.
+        @raise HTTPError: If C{url} contains a query or fragment.
+            The contained response will have a status code of
+            L{responsecode.BAD_REQUEST}.
+        """
+        if url is None:
+            return defer.succeed(None)
+
+        #
+        # Parse the URL
+        #
+        (scheme, host, path, query, fragment) = urlsplit(url)
+
+        if query or fragment:
+            raise http.HTTPError(http.StatusResponse(
+                responsecode.BAD_REQUEST,
+                "URL may not contain a query or fragment: %s" % (url,)
+            ))
+
+        # The caller shouldn't be asking a request on one server to lookup a
+        # resource on some other server.
+        if (scheme and scheme != self.scheme) or (host and host != self.headers.getHeader("host")):
+            raise http.HTTPError(http.StatusResponse(
+                responsecode.BAD_GATEWAY,
+                "URL is not on this site (%s://%s/): %s" % (scheme, self.headers.getHeader("host"), url)
+            ))
+
+        # 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,)
+
+        # Walk the segments up to see if we can find a cached resource to start from
+        preSegments = segments[:-1]
+        postSegments = segments[-1:]
+        cachedParent = None
+        while(len(preSegments)):
+            parentPath = "/".join(preSegments) + "/"
+            cachedParent = self._resourcesByURL.get(parentPath, None)
+            if cachedParent is not None:
+                break
+            else:
+                postSegments.insert(0, preSegments.pop())
+        
+        if cachedParent is None:
+            cachedParent = self.site.resource
+            postSegments = segments[1:]
+
+        def notFound(f):
+            f.trap(http.HTTPError)
+            if f.value.response.code != responsecode.NOT_FOUND:
+                return f
+            return None
+
+        d = defer.maybeDeferred(self._getChild, None, cachedParent, postSegments, updatepaths=False)
+        d.addCallback(self._rememberResource, path)
+        d.addErrback(notFound)
+        return d
+
+    def locateChildResource(self, parent, childName):
+        """
+        Looks up the child resource with the given name given the parent
+        resource.  This is similar to locateResource(), but doesn't have to
+        start the lookup from the root resource, so it is potentially faster.
+        @param parent: the parent of the resource being looked up.  This resource
+            must have been obtained from the request (ie. via its C{uri} attribute,
+            or through its C{locateResource} or C{locateChildResource} methods).
+        @param childName: the name of the child of C{parent} to looked up.
+            to C{parent}.
+        @return: a L{Deferred} resulting in the L{IResource} at the
+            given URL or C{None} if no such resource can be located.
+        @raise NoURLForResourceError: if C{resource} was not obtained from the
+            request.
+        """
+        if parent is None or childName is None:
+            return None
+
+        assert "/" not in childName, "Child name may not contain '/': %s" % (childName,)
+
+        parentURL = self.urlForResource(parent)
+        if not parentURL.endswith("/"):
+            parentURL += "/"
+        url = parentURL + quote(childName)
+
+        segment = childName
+
+        def notFound(f):
+            f.trap(http.HTTPError)
+            if f.value.response.code != responsecode.NOT_FOUND:
+                return f
+            return None
+
+        d = defer.maybeDeferred(self._getChild, None, parent, [segment], updatepaths=False)
+        d.addCallback(self._rememberResource, url)
+        d.addErrback(notFound)
+        return d
+
+    def _processingFailed(self, reason):
+        if reason.check(http.HTTPError) is not None:
+            # If the exception was an HTTPError, leave it alone
+            d = defer.succeed(reason.value.response)
+        else:
+            # Otherwise, it was a random exception, so give a
+            # ICanHandleException implementer a chance to render the page.
+            def _processingFailed_inner(reason):
+                handler = iweb.ICanHandleException(self, self)
+                return handler.renderHTTP_exception(self, reason)
+            d = defer.maybeDeferred(_processingFailed_inner, reason)
+
+        d.addCallback(self._cbFinishRender)
+        d.addErrback(self._processingReallyFailed, reason)
+        return d
+
+    def _processingReallyFailed(self, reason, origReason):
+        log.msg("Exception rendering error page:", isErr=1)
+        log.err(reason)
+        log.msg("Original exception:", isErr=1)
+        log.err(origReason)
+
+        body = ("<html><head><title>Internal Server Error</title></head>"
+                "<body><h1>Internal Server Error</h1>An error occurred rendering the requested page. Additionally, an error occured rendering the error page.</body></html>")
+
+        response = http.Response(
+            responsecode.INTERNAL_SERVER_ERROR,
+            {'content-type': http_headers.MimeType('text','html')},
+            body)
+        self.writeResponse(response)
+
+    def _cbFinishRender(self, result):
+        def filterit(response, f):
+            if (hasattr(f, 'handleErrors') or
+                (response.code >= 200 and response.code < 300)):
+                return f(self, response)
+            else:
+                return response
+
+        response = iweb.IResponse(result, None)
+        if response:
+            d = defer.Deferred()
+            for f in self.responseFilters:
+                d.addCallback(filterit, f)
+            d.addCallback(self.writeResponse)
+            d.callback(response)
+            return d
+
+        resource = iweb.IResource(result, None)
+        if resource:
+            self.resources.append(resource)
+            d = defer.maybeDeferred(resource.renderHTTP, self)
+            d.addCallback(self._cbFinishRender)
+            return d
+
+        raise TypeError("html is not a resource or a response")
+
+    def renderHTTP_exception(self, req, reason):
+        log.msg("Exception rendering:", isErr=1)
+        log.err(reason)
+
+        body = ("<html><head><title>Internal Server Error</title></head>"
+                "<body><h1>Internal Server Error</h1>An error occurred rendering the requested page. More information is available in the server log.</body></html>")
+
+        return http.Response(
+            responsecode.INTERNAL_SERVER_ERROR,
+            {'content-type': http_headers.MimeType('text','html')},
+            body)
+
+class Site(object):
+    def __init__(self, resource):
+        """Initialize.
+        """
+        self.resource = iweb.IResource(resource)
+
+    def __call__(self, *args, **kwargs):
+        return Request(site=self, *args, **kwargs)
+
+
+class NoURLForResourceError(RuntimeError):
+    def __init__(self, resource):
+        RuntimeError.__init__(self, "Resource %r has no URL in this request." % (resource,))
+        self.resource = resource
+
+
+__all__ = ['Request', 'Site', 'StopTraversal', 'VERSION', 'defaultHeadersFilter', 'doTrace', 'parsePOSTData', 'preconditionfilter', 'NoURLForResourceError']

Copied: CalendarServer/trunk/twext/web2/static.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/static.py)
===================================================================
--- CalendarServer/trunk/twext/web2/static.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/static.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,632 @@
+##
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.
+#
+##
+
+
+"""
+I deal with static resources.
+"""
+
+# System Imports
+import os, time
+import tempfile
+
+# Sibling Imports
+from twext.web2 import http_headers, resource
+from twext.web2 import http, iweb, stream, responsecode, server, dirlist
+
+# Twisted Imports
+from twext.python.filepath import CachingFilePath as FilePath
+from twisted.internet.defer import maybeDeferred
+from zope.interface import implements
+
+class MetaDataMixin(object):
+    """
+    Mix-in class for L{iweb.IResource} which provides methods for accessing resource
+    metadata specified by HTTP.
+    """
+    def etag(self):
+        """
+        @return: The current etag for the resource if available, None otherwise.
+        """
+        return None
+
+    def lastModified(self):
+        """
+        @return: The last modified time of the resource if available, None otherwise.
+        """
+        return None
+
+    def creationDate(self):
+        """
+        @return: The creation date of the resource if available, None otherwise.
+        """
+        return None
+
+    def contentLength(self):
+        """
+        @return: The size in bytes of the resource if available, None otherwise.
+        """
+        return None
+
+    def contentType(self):
+        """
+        @return: The MIME type of the resource if available, None otherwise.
+        """
+        return None
+
+    def contentEncoding(self):
+        """
+        @return: The encoding of the resource if available, None otherwise.
+        """
+        return None
+
+    def displayName(self):
+        """
+        @return: The display name of the resource if available, None otherwise.
+        """
+        return None
+
+    def exists(self):
+        """
+        @return: True if the resource exists on the server, False otherwise.
+        """
+        return True
+
+class StaticRenderMixin(resource.RenderMixin, MetaDataMixin):
+    def checkPreconditions(self, request):
+        # This code replaces the code in resource.RenderMixin
+        if request.method not in ("GET", "HEAD"):
+            http.checkPreconditions(
+                request,
+                entityExists = self.exists(),
+                etag = self.etag(),
+                lastModified = self.lastModified(),
+            )
+
+        # Check per-method preconditions
+        method = getattr(self, "preconditions_" + request.method, None)
+        if method:
+            return method(request)
+
+    def renderHTTP(self, request):
+        """
+        See L{resource.RenderMixIn.renderHTTP}.
+
+        This implementation automatically sets some headers on the response
+        based on data available from L{MetaDataMixin} methods.
+        """
+        def setHeaders(response):
+            response = iweb.IResponse(response)
+
+            # Don't provide additional resource information to error responses
+            if response.code < 400:
+                # Content-* headers refer to the response content, not
+                # (necessarily) to the resource content, so they depend on the
+                # request method, and therefore can't be set here.
+                for (header, value) in (
+                    ("etag", self.etag()),
+                    ("last-modified", self.lastModified()),
+                ):
+                    if value is not None:
+                        response.headers.setHeader(header, value)
+
+            return response
+
+        def onError(f):
+            # If we get an HTTPError, run its response through setHeaders() as
+            # well.
+            f.trap(http.HTTPError)
+            return setHeaders(f.value.response)
+
+        d = maybeDeferred(super(StaticRenderMixin, self).renderHTTP, request)
+        return d.addCallbacks(setHeaders, onError)
+
+class Data(resource.Resource):
+    """
+    This is a static, in-memory resource.
+    """
+    def __init__(self, data, type):
+        self.data = data
+        self.type = http_headers.MimeType.fromString(type)
+        self.created_time = time.time()
+
+    def etag(self):
+        lastModified = self.lastModified()
+        return http_headers.ETag("%X-%X" % (lastModified, hash(self.data)),
+                                 weak=(time.time() - lastModified <= 1))
+
+    def lastModified(self):
+        return self.creationDate()
+
+    def creationDate(self):
+        return self.created_time
+
+    def contentLength(self):
+        return len(self.data)
+
+    def contentType(self):
+        return self.type
+
+    def render(self, req):
+        return http.Response(
+            responsecode.OK,
+            http_headers.Headers({'content-type': self.contentType()}),
+            stream=self.data)
+
+
+class File(StaticRenderMixin):
+    """
+    File is a resource that represents a plain non-interpreted file
+    (although it can look for an extension like .rpy or .cgi and hand the
+    file to a processor for interpretation if you wish). Its constructor
+    takes a file path.
+
+    Alternatively, you can give a directory path to the constructor. In this
+    case the resource will represent that directory, and its children will
+    be files underneath that directory. This provides access to an entire
+    filesystem tree with a single Resource.
+
+    If you map the URL 'http://server/FILE' to a resource created as
+    File('/tmp'), then http://server/FILE/ will return an HTML-formatted
+    listing of the /tmp/ directory, and http://server/FILE/foo/bar.html will
+    return the contents of /tmp/foo/bar.html .
+    """
+    implements(iweb.IResource)
+
+    def _getContentTypes(self):
+        if not hasattr(File, "_sharedContentTypes"):
+            File._sharedContentTypes = loadMimeTypes()
+        return File._sharedContentTypes
+
+    contentTypes = property(_getContentTypes)
+
+    contentEncodings = {
+        ".gz" : "gzip",
+        ".bz2": "bzip2"
+        }
+
+    processors = {}
+
+    indexNames = ["index", "index.html", "index.htm", "index.trp", "index.rpy"]
+
+    type = None
+
+    def __init__(self, path, defaultType="text/plain", ignoredExts=(), processors=None, indexNames=None):
+        """Create a file with the given path.
+        """
+        super(File, self).__init__()
+
+        self.putChildren = {}
+        if isinstance(path, FilePath):
+            self.fp = path
+        else:
+            assert isinstance(path, str), "This should be a string."
+            self.fp = FilePath(path)
+        # Remove the dots from the path to split
+        self.defaultType = defaultType
+        self.ignoredExts = list(ignoredExts)
+        if processors is not None:
+            self.processors = dict([
+                (key.lower(), value)
+                for key, value in processors.items()
+                ])
+
+        if indexNames is not None:
+            self.indexNames = indexNames
+
+    def comparePath(self, path):
+        
+        if isinstance(path, FilePath):
+            return path.path == self.fp.path
+        else:
+            return path == self.fp.path
+
+    def exists(self):
+        return self.fp.exists()
+
+    def etag(self):
+        if not self.fp.exists(): return None
+
+        st = self.fp.statinfo
+
+        #
+        # Mark ETag as weak if it was modified more recently than we can
+        # measure and report, as it could be modified again in that span
+        # and we then wouldn't know to provide a new ETag.
+        #
+        weak = (time.time() - st.st_mtime <= 1)
+
+        return http_headers.ETag(
+            "%X-%X-%X" % (st.st_ino, st.st_size, st.st_mtime),
+            weak=weak
+        )
+
+    def lastModified(self):
+        if self.fp.exists():
+            return self.fp.getmtime()
+        else:
+            return None
+
+    def creationDate(self):
+        if self.fp.exists():
+            return self.fp.getmtime()
+        else:
+            return None
+
+    def contentLength(self):
+        if self.fp.exists():
+            if self.fp.isfile():
+                return self.fp.getsize()
+            else:
+                # Computing this would require rendering the resource; let's
+                # punt instead.
+                return None
+        else:
+            return None
+
+    def _initTypeAndEncoding(self):
+        self._type, self._encoding = getTypeAndEncoding(
+            self.fp.basename(),
+            self.contentTypes,
+            self.contentEncodings,
+            self.defaultType
+        )
+
+        # Handle cases not covered by getTypeAndEncoding()
+        if self.fp.isdir(): self._type = "httpd/unix-directory"
+
+    def contentType(self):
+        if not hasattr(self, "_type"):
+            self._initTypeAndEncoding()
+        return http_headers.MimeType.fromString(self._type)
+
+    def contentEncoding(self):
+        if not hasattr(self, "_encoding"):
+            self._initTypeAndEncoding()
+        return self._encoding
+
+    def displayName(self):
+        if self.fp.exists():
+            return self.fp.basename()
+        else:
+            return None
+
+    def ignoreExt(self, ext):
+        """Ignore the given extension.
+
+        Serve file.ext if file is requested
+        """
+        self.ignoredExts.append(ext)
+
+    def directoryListing(self):
+        return dirlist.DirectoryLister(self.fp.path,
+                                       self.listChildren(),
+                                       self.contentTypes,
+                                       self.contentEncodings,
+                                       self.defaultType)
+
+    def putChild(self, name, child):
+        """
+        Register a child with the given name with this resource.
+        @param name: the name of the child (a URI path segment)
+        @param child: the child to register
+        """
+        self.putChildren[name] = child
+
+    def getChild(self, name):
+        """
+        Look up a child resource.
+        @return: the child of this resource with the given name.
+        """
+        if name == "":
+            return self
+
+        child = self.putChildren.get(name, None)
+        if child: return child
+
+        child_fp = self.fp.child(name)
+        if hasattr(self, "knownChildren"):
+            if name in self.knownChildren:
+                child_fp.existsCached = True
+        if child_fp.exists():
+            return self.createSimilarFile(child_fp)
+        else:
+            return None
+
+    def listChildren(self):
+        """
+        @return: a sequence of the names of all known children of this resource.
+        """
+        children = self.putChildren.keys()
+        if self.fp.isdir():
+            children += [c for c in self.fp.listdir() if c not in children]
+            self.knownChildren = set(children)
+        return children
+
+    def locateChild(self, req, segments):
+        """
+        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:])
+
+        # If we're not backed by a directory, we have no children.
+        # But check for existance first; we might be a collection resource
+        # that the request wants created.
+        self.fp.restat(False)
+        if self.fp.exists() and not self.fp.isdir(): return (None, ())
+
+        # OK, we need to return a child corresponding to the first segment
+        path = segments[0]
+
+        if path:
+            fpath = self.fp.child(path)
+        else:
+            # Request is for a directory (collection) resource
+            return (self, server.StopTraversal)
+
+        # Don't run processors on directories - if someone wants their own
+        # customized directory rendering, subclass File instead.
+        if fpath.isfile():
+            processor = self.processors.get(fpath.splitext()[1].lower())
+            if processor:
+                return (
+                    processor(fpath.path),
+                    segments[1:])
+
+        elif not fpath.exists():
+            sibling_fpath = fpath.siblingExtensionSearch(*self.ignoredExts)
+            if sibling_fpath is not None:
+                fpath = sibling_fpath
+
+        return self.createSimilarFile(fpath.path), segments[1:]
+
+    def renderHTTP(self, req):
+        self.fp.changed()
+        return super(File, self).renderHTTP(req)
+
+    def render(self, req):
+        """You know what you doing."""
+        if not self.fp.exists():
+            return responsecode.NOT_FOUND
+
+        if self.fp.isdir():
+            if req.path[-1] != "/":
+                # Redirect to include trailing '/' in URI
+                return http.RedirectResponse(req.unparseURL(path=req.path+'/'))
+            else:
+                ifp = self.fp.childSearchPreauth(*self.indexNames)
+                if ifp:
+                    # Render from the index file
+                    standin = self.createSimilarFile(ifp.path)
+                else:
+                    # Render from a DirectoryLister
+                    standin = dirlist.DirectoryLister(
+                        self.fp.path,
+                        self.listChildren(),
+                        self.contentTypes,
+                        self.contentEncodings,
+                        self.defaultType
+                    )
+                return standin.render(req)
+
+        try:
+            f = self.fp.open()
+        except IOError, e:
+            import errno
+            if e[0] == errno.EACCES:
+                return responsecode.FORBIDDEN
+            elif e[0] == errno.ENOENT:
+                return responsecode.NOT_FOUND
+            else:
+                raise
+
+        response = http.Response()
+        response.stream = stream.FileStream(f, 0, self.fp.getsize())
+
+        for (header, value) in (
+            ("content-type", self.contentType()),
+            ("content-encoding", self.contentEncoding()),
+        ):
+            if value is not None:
+                response.headers.setHeader(header, value)
+
+        return response
+
+    def createSimilarFile(self, path):
+        return self.__class__(path, self.defaultType, self.ignoredExts,
+                              self.processors, self.indexNames[:])
+
+
+class FileSaver(resource.PostableResource):
+    allowedTypes = (http_headers.MimeType('text', 'plain'),
+                    http_headers.MimeType('text', 'html'),
+                    http_headers.MimeType('text', 'css'))
+
+    def __init__(self, destination, expectedFields=[], allowedTypes=None, maxBytes=1000000, permissions=0644):
+        self.destination = destination
+        self.allowedTypes = allowedTypes or self.allowedTypes
+        self.maxBytes = maxBytes
+        self.expectedFields = expectedFields
+        self.permissions = permissions
+
+    def makeUniqueName(self, filename):
+        """Called when a unique filename is needed.
+
+        filename is the name of the file as given by the client.
+
+        Returns the fully qualified path of the file to create. The
+        file must not yet exist.
+        """
+
+        return tempfile.mktemp(suffix=os.path.splitext(filename)[1], dir=self.destination)
+
+    def isSafeToWrite(self, filename, mimetype, filestream):
+        """Returns True if it's "safe" to write this file,
+        otherwise it raises an exception.
+        """
+
+        if filestream.length > self.maxBytes:
+            raise IOError("%s: File exceeds maximum length (%d > %d)" % (filename,
+                                                                         filestream.length,
+                                                                         self.maxBytes))
+
+        if mimetype not in self.allowedTypes:
+            raise IOError("%s: File type not allowed %s" % (filename, mimetype))
+
+        return True
+
+    def writeFile(self, filename, mimetype, fileobject):
+        """Does the I/O dirty work after it calls isSafeToWrite to make
+        sure it's safe to write this file.
+        """
+        filestream = stream.FileStream(fileobject)
+
+        if self.isSafeToWrite(filename, mimetype, filestream):
+            outname = self.makeUniqueName(filename)
+
+            flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0)
+
+            fileobject = os.fdopen(os.open(outname, flags, self.permissions), 'wb', 0)
+                
+            stream.readIntoFile(filestream, fileobject)
+
+        return outname
+
+    def render(self, req):
+        content = ["<html><body>"]
+
+        if req.files:
+            for fieldName in req.files:
+                if fieldName in self.expectedFields:
+                    for finfo in req.files[fieldName]:
+                        try:
+                            outname = self.writeFile(*finfo)
+                            content.append("Saved file %s<br />" % outname)
+                        except IOError, err:
+                            content.append(str(err) + "<br />")
+                else:
+                    content.append("%s is not a valid field" % fieldName)
+
+        else:
+            content.append("No files given")
+
+        content.append("</body></html>")
+
+        return http.Response(responsecode.OK, {}, stream='\n'.join(content))
+
+
+# FIXME: hi there I am a broken class
+# """I contain AsIsProcessor, which serves files 'As Is'
+#    Inspired by Apache's mod_asis
+# """
+#
+# class ASISProcessor:
+#     implements(iweb.IResource)
+#
+#     def __init__(self, path):
+#         self.path = path
+#
+#     def renderHTTP(self, request):
+#         request.startedWriting = 1
+#         return File(self.path)
+#
+#     def locateChild(self, request):
+#         return None, ()
+
+##
+# Utilities
+##
+
+dangerousPathError = http.HTTPError(responsecode.NOT_FOUND) #"Invalid request URL."
+
+def isDangerous(path):
+    return path == '..' or '/' in path or os.sep in path
+
+def addSlash(request):
+    return "http%s://%s%s/" % (
+        request.isSecure() and 's' or '',
+        request.getHeader("host"),
+        (request.uri.split('?')[0]))
+
+def loadMimeTypes(mimetype_locations=['/etc/mime.types']):
+    """
+    Multiple file locations containing mime-types can be passed as a list.
+    The files will be sourced in that order, overriding mime-types from the
+    files sourced beforehand, but only if a new entry explicitly overrides
+    the current entry.
+    """
+    import mimetypes
+    # Grab Python's built-in mimetypes dictionary.
+    contentTypes = mimetypes.types_map
+    # Update Python's semi-erroneous dictionary with a few of the
+    # usual suspects.
+    contentTypes.update(
+        {
+            '.conf':  'text/plain',
+            '.diff':  'text/plain',
+            '.exe':   'application/x-executable',
+            '.flac':  'audio/x-flac',
+            '.java':  'text/plain',
+            '.ogg':   'application/ogg',
+            '.oz':    'text/x-oz',
+            '.swf':   'application/x-shockwave-flash',
+            '.tgz':   'application/x-gtar',
+            '.wml':   'text/vnd.wap.wml',
+            '.xul':   'application/vnd.mozilla.xul+xml',
+            '.py':    'text/plain',
+            '.patch': 'text/plain',
+        }
+    )
+    # Users can override these mime-types by loading them out configuration
+    # files (this defaults to ['/etc/mime.types']).
+    for location in mimetype_locations:
+        if os.path.exists(location):
+            contentTypes.update(mimetypes.read_mime_types(location))
+
+    return contentTypes
+
+def getTypeAndEncoding(filename, types, encodings, defaultType):
+    p, ext = os.path.splitext(filename)
+    ext = ext.lower()
+    if encodings.has_key(ext):
+        enc = encodings[ext]
+        ext = os.path.splitext(p)[1].lower()
+    else:
+        enc = None
+    type = types.get(ext, defaultType)
+    return type, enc
+
+##
+# Test code
+##
+
+if __name__ == '__builtin__':
+    # Running from twistd -y
+    from twisted.application import service, strports
+    res = File('/')
+    application = service.Application("demo")
+    s = strports.service('8080', server.Site(res))
+    s.setServiceParent(application)

Copied: CalendarServer/trunk/twext/web2/stream.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/stream.py)
===================================================================
--- CalendarServer/trunk/twext/web2/stream.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/stream.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,1106 @@
+# -*- test-case-name: twext.web2.test.test_stream -*-
+##
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# Copyright (c) 2010 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.
+#
+##
+
+"""
+The stream module provides a simple abstraction of streaming
+data. While Twisted already has some provisions for handling this in
+its Producer/Consumer model, the rather complex interactions between
+producer and consumer makes it difficult to implement something like
+the CompoundStream object. Thus, this API.
+
+The IStream interface is very simple. It consists of two methods:
+read, and close. The read method should either return some data, None
+if there is no data left to read, or a Deferred. Close frees up any
+underlying resources and causes read to return None forevermore.
+
+IByteStream adds a bit more to the API:
+1) read is required to return objects conforming to the buffer interface.  
+2) .length, which may either an integer number of bytes remaining, or
+None if unknown
+3) .split(position). Split takes a position, and splits the
+stream in two pieces, returning the two new streams. Using the
+original stream after calling split is not allowed. 
+
+There are two builtin source stream classes: FileStream and
+MemoryStream. The first produces data from a file object, the second
+from a buffer in memory. Any number of these can be combined into one
+stream with the CompoundStream object. Then, to interface with other
+parts of Twisted, there are two transcievers: StreamProducer and
+ProducerStream. The first takes a stream and turns it into an
+IPushProducer, which will write to a consumer. The second is a
+consumer which is a stream, so that other producers can write to it.
+"""
+
+from __future__ import generators
+
+import copy, os, types, sys
+from zope.interface import Interface, Attribute, implements
+from twisted.internet.defer import Deferred
+from twisted.internet import interfaces as ti_interfaces, defer, reactor, protocol, error as ti_error
+from twisted.python import components, log
+from twisted.python.failure import Failure
+
+# Python 2.4.2 (only) has a broken mmap that leaks a fd every time you call it.
+if sys.version_info[0:3] != (2,4,2):
+    try:
+        import mmap
+    except ImportError:
+        mmap = None
+else:
+    mmap = None
+    
+##############################
+####      Interfaces      ####
+##############################
+
+class IStream(Interface):
+    """A stream of arbitrary data."""
+    
+    def read():
+        """Read some data.
+
+        Returns some object representing the data.
+        If there is no more data available, returns None.
+        Can also return a Deferred resulting in one of the above.
+
+        Errors may be indicated by exception or by a Deferred of a Failure.
+        """
+        
+    def close():
+        """Prematurely close. Should also cause further reads to
+        return None."""
+
+class IByteStream(IStream):
+    """A stream which is of bytes."""
+    
+    length = Attribute("""How much data is in this stream. Can be None if unknown.""")
+    
+    def read():
+        """Read some data.
+        
+        Returns an object conforming to the buffer interface, or
+        if there is no more data available, returns None.
+        Can also return a Deferred resulting in one of the above.
+
+        Errors may be indicated by exception or by a Deferred of a Failure.
+        """
+    def split(point):
+        """Split this stream into two, at byte position 'point'.
+
+        Returns a tuple of (before, after). After calling split, no other
+        methods should be called on this stream. Doing so will have undefined
+        behavior.
+
+        If you cannot implement split easily, you may implement it as::
+
+            return fallbackSplit(self, point)
+        """
+
+    def close():
+        """Prematurely close this stream. Should also cause further reads to
+        return None. Additionally, .length should be set to 0.
+        """
+
+class ISendfileableStream(Interface):
+    def read(sendfile=False):
+        """
+        Read some data.
+        If sendfile == False, returns an object conforming to the buffer
+        interface, or else a Deferred.
+
+        If sendfile == True, returns either the above, or a SendfileBuffer.
+        """
+        
+class SimpleStream(object):
+    """Superclass of simple streams with a single buffer and a offset and length
+    into that buffer."""
+    implements(IByteStream)
+    
+    length = None
+    start = None
+    
+    def read(self):
+        return None
+
+    def close(self):
+        self.length = 0
+    
+    def split(self, point):
+        if self.length is not None:
+            if point > self.length:
+                raise ValueError("split point (%d) > length (%d)" % (point, self.length))
+        b = copy.copy(self)
+        self.length = point
+        if b.length is not None:
+            b.length -= point
+        b.start += point
+        return (self, b)
+
+##############################
+####      FileStream      ####
+##############################
+    
+# maximum mmap size
+MMAP_LIMIT = 4*1024*1024
+# minimum mmap size
+MMAP_THRESHOLD = 8*1024
+
+# maximum sendfile length
+SENDFILE_LIMIT = 16777216
+# minimum sendfile size
+SENDFILE_THRESHOLD = 256
+
+def mmapwrapper(*args, **kwargs):
+    """
+    Python's mmap call sucks and ommitted the "offset" argument for no
+    discernable reason. Replace this with a mmap module that has offset.
+    """
+    
+    offset = kwargs.get('offset', None)
+    if offset in [None, 0]:
+        if 'offset' in kwargs:
+            del kwargs['offset']
+    else:
+        raise mmap.error("mmap: Python sucks and does not support offset.")
+    return mmap.mmap(*args, **kwargs)
+
+class FileStream(SimpleStream):
+    implements(ISendfileableStream)
+    """A stream that reads data from a file. File must be a normal
+    file that supports seek, (e.g. not a pipe or device or socket)."""
+    # 65K, minus some slack
+    CHUNK_SIZE = 2 ** 2 ** 2 ** 2 - 32
+
+    f = None
+    def __init__(self, f, start=0, length=None, useMMap=bool(mmap)):
+        """
+        Create the stream from file f. If you specify start and length,
+        use only that portion of the file.
+        """
+        self.f = f
+        self.start = start
+        if length is None:
+            self.length = os.fstat(f.fileno()).st_size
+        else:
+            self.length = length
+        self.useMMap = useMMap
+        
+    def read(self, sendfile=False):
+        if self.f is None:
+            return None
+
+        length = self.length
+        if length == 0:
+            self.f = None
+            return None
+
+        if sendfile and length > SENDFILE_THRESHOLD:
+            # XXX: Yay using non-existent sendfile support!
+            # FIXME: if we return a SendfileBuffer, and then sendfile
+            #        fails, then what? Or, what if file is too short?
+            readSize = min(length, SENDFILE_LIMIT)
+            res = SendfileBuffer(self.f, self.start, readSize)
+            self.length -= readSize
+            self.start += readSize
+            return res
+
+        if self.useMMap and length > MMAP_THRESHOLD:
+            readSize = min(length, MMAP_LIMIT)
+            try:
+                res = mmapwrapper(self.f.fileno(), readSize,
+                                  access=mmap.ACCESS_READ, offset=self.start)
+                #madvise(res, MADV_SEQUENTIAL)
+                self.length -= readSize
+                self.start += readSize
+                return res
+            except mmap.error:
+                pass
+
+        # Fall back to standard read.
+        readSize = min(length, self.CHUNK_SIZE)
+
+        self.f.seek(self.start)
+        b = self.f.read(readSize)
+        bytesRead = len(b)
+        if not bytesRead:
+            raise RuntimeError("Ran out of data reading file %r, expected %d more bytes" % (self.f, length))
+        else:
+            self.length -= bytesRead
+            self.start += bytesRead
+            return b
+
+    def close(self):
+        self.f = None
+        SimpleStream.close(self)
+
+components.registerAdapter(FileStream, file, IByteStream)
+
+##############################
+####     MemoryStream     ####
+##############################
+
+class MemoryStream(SimpleStream):
+    """A stream that reads data from a buffer object."""
+    def __init__(self, mem, start=0, length=None):
+        """
+        Create the stream from buffer object mem. If you specify start and length,
+        use only that portion of the buffer.
+        """
+        self.mem = mem
+        self.start = start
+        if length is None:
+            self.length = len(mem) - start
+        else:
+            if len(mem) < length:
+                raise ValueError("len(mem) < start + length")
+            self.length = length
+
+    def read(self):
+        if self.mem is None:
+            return None
+        if self.length == 0:
+            result = None
+        else:
+            result = buffer(self.mem, self.start, self.length)
+        self.mem = None
+        self.length = 0
+        return result
+
+    def close(self):
+        self.mem = None
+        SimpleStream.close(self)
+
+components.registerAdapter(MemoryStream, str, IByteStream)
+components.registerAdapter(MemoryStream, types.BufferType, IByteStream)
+
+##############################
+####    CompoundStream    ####
+##############################
+
+class CompoundStream(object):
+    """A stream which is composed of many other streams.
+
+    Call addStream to add substreams.
+    """
+    
+    implements(IByteStream, ISendfileableStream)
+    deferred = None
+    length = 0
+    
+    def __init__(self, buckets=()):
+        self.buckets = [IByteStream(s) for s in buckets]
+        
+    def addStream(self, bucket):
+        """Add a stream to the output"""
+        bucket = IByteStream(bucket)
+        self.buckets.append(bucket)
+        if self.length is not None:
+            if bucket.length is None:
+                self.length = None
+            else:
+                self.length += bucket.length
+
+    def read(self, sendfile=False):
+        if self.deferred is not None:
+            raise RuntimeError("Call to read while read is already outstanding")
+
+        if not self.buckets:
+            return None
+        
+        if sendfile and ISendfileableStream.providedBy(self.buckets[0]):
+            try:
+                result = self.buckets[0].read(sendfile)
+            except:
+                return self._gotFailure(Failure())
+        else:
+            try:
+                result = self.buckets[0].read()
+            except:
+                return self._gotFailure(Failure())
+        
+        if isinstance(result, Deferred):
+            self.deferred = result
+            result.addCallbacks(self._gotRead, self._gotFailure, (sendfile,))
+            return result
+        
+        return self._gotRead(result, sendfile)
+
+    def _gotFailure(self, f):
+        self.deferred = None
+        del self.buckets[0]
+        self.close()
+        return f
+    
+    def _gotRead(self, result, sendfile):
+        self.deferred = None
+        if result is None:
+            del self.buckets[0]
+            # Next bucket
+            return self.read(sendfile)
+        
+        if self.length is not None:
+            self.length -= len(result)
+        return result
+    
+    def split(self, point):
+        num = 0
+        origPoint = point
+        for bucket in self.buckets:
+            num+=1
+
+            if point == 0:
+                b = CompoundStream()
+                b.buckets = self.buckets[num:]
+                del self.buckets[num:]
+                return self,b
+            
+            if bucket.length is None:
+                # Indeterminate length bucket.
+                # give up and use fallback splitter.
+                return fallbackSplit(self, origPoint)
+            
+            if point < bucket.length:
+                before,after = bucket.split(point)
+                b = CompoundStream()
+                b.buckets = self.buckets[num:]
+                b.buckets[0] = after
+                
+                del self.buckets[num+1:]
+                self.buckets[num] = before
+                return self,b
+            
+            point -= bucket.length
+    
+    def close(self):
+        for bucket in self.buckets:
+            bucket.close()
+        self.buckets = []
+        self.length = 0
+
+
+##############################
+####      readStream      ####
+##############################
+
+class _StreamReader(object):
+    """Process a stream's data using callbacks for data and stream finish."""
+
+    def __init__(self, stream, gotDataCallback):
+        self.stream = stream
+        self.gotDataCallback = gotDataCallback
+        self.result = Deferred()
+
+    def run(self):
+        # self.result may be del'd in _read()
+        result = self.result
+        self._read()
+        return result
+    
+    def _read(self):
+        try:
+            result = self.stream.read()
+        except:
+            self._gotError(Failure())
+            return
+        if isinstance(result, Deferred):
+            result.addCallbacks(self._gotData, self._gotError)
+        else:
+            self._gotData(result)
+
+    def _gotError(self, failure):
+        result = self.result
+        del self.result, self.gotDataCallback, self.stream
+        result.errback(failure)
+    
+    def _gotData(self, data):
+        if data is None:
+            result = self.result
+            del self.result, self.gotDataCallback, self.stream
+            result.callback(None)
+            return
+        try:
+            self.gotDataCallback(data)
+        except:
+            self._gotError(Failure())
+            return
+        reactor.callLater(0, self._read)
+
+def readStream(stream, gotDataCallback):
+    """Pass a stream's data to a callback.
+
+    Returns Deferred which will be triggered on finish.  Errors in
+    reading the stream or in processing it will be returned via this
+    Deferred.
+    """
+    return _StreamReader(stream, gotDataCallback).run()
+
+
+def readAndDiscard(stream):
+    """Read all the data from the given stream, and throw it out.
+
+    Returns Deferred which will be triggered on finish.
+    """
+    return readStream(stream, lambda _: None)
+
+def readIntoFile(stream, outFile):
+    """Read a stream and write it into a file.
+
+    Returns Deferred which will be triggered on finish.
+    """
+    def done(_):
+        outFile.close()
+        return _
+    return readStream(stream, outFile.write).addBoth(done)
+
+def connectStream(inputStream, factory):
+    """Connect a protocol constructed from a factory to stream.
+
+    Returns an output stream from the protocol.
+
+    The protocol's transport will have a finish() method it should
+    call when done writing.
+    """
+    # XXX deal better with addresses
+    p = factory.buildProtocol(None)
+    out = ProducerStream()
+    out.disconnecting = False # XXX for LineReceiver suckage
+    p.makeConnection(out)
+    readStream(inputStream, lambda _: p.dataReceived(_)).addCallbacks(
+        lambda _: p.connectionLost(ti_error.ConnectionDone()), lambda _: p.connectionLost(_))
+    return out
+
+##############################
+####     fallbackSplit    ####
+##############################
+
+def fallbackSplit(stream, point):
+    after = PostTruncaterStream(stream, point)
+    before = TruncaterStream(stream, point, after)
+    return (before, after)
+
+class TruncaterStream(object):
+    def __init__(self, stream, point, postTruncater):
+        self.stream = stream
+        self.length = point
+        self.postTruncater = postTruncater
+        
+    def read(self):
+        if self.length == 0:
+            if self.postTruncater is not None:
+                postTruncater = self.postTruncater
+                self.postTruncater = None
+                postTruncater.sendInitialSegment(self.stream.read())
+            self.stream = None
+            return None
+        
+        result = self.stream.read()
+        if isinstance(result, Deferred):
+            return result.addCallback(self._gotRead)
+        else:
+            return self._gotRead(result)
+        
+    def _gotRead(self, data):
+        if data is None:
+            raise ValueError("Ran out of data for a split of a indeterminate length source")
+        if self.length >= len(data):
+            self.length -= len(data)
+            return data
+        else:
+            before = buffer(data, 0, self.length)
+            after = buffer(data, self.length)
+            self.length = 0
+            if self.postTruncater is not None:
+                postTruncater = self.postTruncater
+                self.postTruncater = None
+                postTruncater.sendInitialSegment(after)
+                self.stream = None
+            return before
+    
+    def split(self, point):
+        if point > self.length:
+            raise ValueError("split point (%d) > length (%d)" % (point, self.length))
+
+        post = PostTruncaterStream(self.stream, point)
+        trunc = TruncaterStream(post, self.length - point, self.postTruncater)
+        self.length = point
+        self.postTruncater = post
+        return self, trunc
+    
+    def close(self):
+        if self.postTruncater is not None:
+            self.postTruncater.notifyClosed(self)
+        else:
+            # Nothing cares about the rest of the stream
+            self.stream.close()
+            self.stream = None
+            self.length = 0
+            
+
+class PostTruncaterStream(object):
+    deferred = None
+    sentInitialSegment = False
+    truncaterClosed = None
+    closed = False
+    
+    length = None
+    def __init__(self, stream, point):
+        self.stream = stream
+        self.deferred = Deferred()
+        if stream.length is not None:
+            self.length = stream.length - point
+
+    def read(self):
+        if not self.sentInitialSegment:
+            self.sentInitialSegment = True
+            if self.truncaterClosed is not None:
+                readAndDiscard(self.truncaterClosed)
+                self.truncaterClosed = None
+            return self.deferred
+        
+        return self.stream.read()
+    
+    def split(self, point):
+        return fallbackSplit(self, point)
+        
+    def close(self):
+        self.closed = True
+        if self.truncaterClosed is not None:
+            # have first half close itself
+            self.truncaterClosed.postTruncater = None
+            self.truncaterClosed.close()
+        elif self.sentInitialSegment:
+            # first half already finished up
+            self.stream.close()
+            
+        self.deferred = None
+    
+    # Callbacks from TruncaterStream
+    def sendInitialSegment(self, data):
+        if self.closed:
+            # First half finished, we don't want data.
+            self.stream.close()
+            self.stream = None
+        if self.deferred is not None:
+            if isinstance(data, Deferred):
+                data.chainDeferred(self.deferred)
+            else:
+                self.deferred.callback(data)
+        
+    def notifyClosed(self, truncater):
+        if self.closed:
+            # we are closed, have first half really close
+            truncater.postTruncater = None
+            truncater.close()
+        elif self.sentInitialSegment:
+            # We are trying to read, read up first half
+            readAndDiscard(truncater)
+        else:
+            # Idle, store closed info.
+            self.truncaterClosed = truncater
+
+########################################
+#### ProducerStream/StreamProducer  ####
+########################################
+            
+class ProducerStream(object):
+    """Turns producers into a IByteStream.
+    Thus, implements IConsumer and IByteStream."""
+
+    implements(IByteStream, ti_interfaces.IConsumer)
+    length = None
+    closed = False
+    failed = False
+    producer = None
+    producerPaused = False
+    deferred = None
+    
+    bufferSize = 5
+    
+    def __init__(self, length=None):
+        self.buffer = []
+        self.length = length
+        
+    # IByteStream implementation
+    def read(self):
+        if self.buffer:
+            return self.buffer.pop(0)
+        elif self.closed:
+            self.length = 0
+            if self.failed:
+                f = self.failure
+                del self.failure
+                return defer.fail(f)
+            return None
+        else:
+            deferred = self.deferred = Deferred()
+            if self.producer is not None and (not self.streamingProducer
+                                              or self.producerPaused):
+                self.producerPaused = False
+                self.producer.resumeProducing()
+                
+            return deferred
+        
+    def split(self, point):
+        return fallbackSplit(self, point)
+    
+    def close(self):
+        """Called by reader of stream when it is done reading."""
+        self.buffer=[]
+        self.closed = True
+        if self.producer is not None:
+            self.producer.stopProducing()
+            self.producer = None
+        self.deferred = None
+        
+    # IConsumer implementation
+    def write(self, data):
+        if self.closed:
+            return
+        
+        if self.deferred:
+            deferred = self.deferred
+            self.deferred = None
+            deferred.callback(data)
+        else:
+            self.buffer.append(data)
+            if(self.producer is not None and self.streamingProducer
+               and len(self.buffer) > self.bufferSize):
+                self.producer.pauseProducing()
+                self.producerPaused = True
+
+    def finish(self, failure=None):
+        """Called by producer when it is done.
+
+        If the optional failure argument is passed a Failure instance,
+        the stream will return it as errback on next Deferred.
+        """
+        self.closed = True
+        if not self.buffer:
+            self.length = 0
+        if self.deferred is not None:
+            deferred = self.deferred
+            self.deferred = None
+            if failure is not None:
+                self.failed = True
+                deferred.errback(failure)
+            else:
+                deferred.callback(None)
+        else:
+            if failure is not None:
+               self.failed = True
+               self.failure = failure
+    
+    def registerProducer(self, producer, streaming):
+        if self.producer is not None:
+            raise RuntimeError("Cannot register producer %s, because producer %s was never unregistered." % (producer, self.producer))
+        
+        if self.closed:
+            producer.stopProducing()
+        else:
+            self.producer = producer
+            self.streamingProducer = streaming
+            if not streaming:
+                producer.resumeProducing()
+
+    def unregisterProducer(self):
+        self.producer = None
+        
+class StreamProducer(object):
+    """A push producer which gets its data by reading a stream."""
+    implements(ti_interfaces.IPushProducer)
+
+    deferred = None
+    finishedCallback = None
+    paused = False
+    consumer = None
+    
+    def __init__(self, stream, enforceStr=True):
+        self.stream = stream
+        self.enforceStr = enforceStr
+        
+    def beginProducing(self, consumer):
+        if self.stream is None:
+            return defer.succeed(None)
+        
+        self.consumer = consumer
+        finishedCallback = self.finishedCallback = Deferred()
+        self.consumer.registerProducer(self, True)
+        self.resumeProducing()
+        return finishedCallback
+    
+    def resumeProducing(self):
+        self.paused = False
+        if self.deferred is not None:
+            return
+
+        try:
+            data = self.stream.read()
+        except:
+            self.stopProducing(Failure())
+            return
+        
+        if isinstance(data, Deferred):
+            self.deferred = data.addCallbacks(self._doWrite, self.stopProducing)
+        else:
+            self._doWrite(data)
+
+    def _doWrite(self, data):
+        if self.consumer is None:
+            return
+        if data is None:
+            # The end.
+            if self.consumer is not None:
+                self.consumer.unregisterProducer()
+            if self.finishedCallback is not None:
+                self.finishedCallback.callback(None)
+            self.finishedCallback = self.deferred = self.consumer = self.stream = None
+            return
+        
+        self.deferred = None
+        if self.enforceStr:
+            # XXX: sucks that we have to do this. make transport.write(buffer) work!
+            data = str(buffer(data))
+        self.consumer.write(data)
+        
+        if not self.paused:
+            self.resumeProducing()
+        
+    def pauseProducing(self):
+        self.paused = True
+
+    def stopProducing(self, failure=ti_error.ConnectionLost()):
+        if self.consumer is not None:
+            self.consumer.unregisterProducer()
+        if self.finishedCallback is not None:
+            if failure is not None:
+                self.finishedCallback.errback(failure)
+            else:
+                self.finishedCallback.callback(None)
+            self.finishedCallback = None
+        self.paused = True
+        if self.stream is not None:
+            self.stream.close()
+            
+        self.finishedCallback = self.deferred = self.consumer = self.stream = None
+
+##############################
+####    ProcessStreamer   ####
+##############################
+
+class _ProcessStreamerProtocol(protocol.ProcessProtocol):
+
+    def __init__(self, inputStream, outStream, errStream):
+        self.inputStream = inputStream
+        self.outStream = outStream
+        self.errStream = errStream
+        self.resultDeferred = defer.Deferred()
+    
+    def connectionMade(self):
+        p = StreamProducer(self.inputStream)
+        # if the process stopped reading from the input stream,
+        # this is not an error condition, so it oughtn't result
+        # in a ConnectionLost() from the input stream:
+        p.stopProducing = lambda err=None: StreamProducer.stopProducing(p, err)
+        
+        d = p.beginProducing(self.transport)
+        d.addCallbacks(lambda _: self.transport.closeStdin(),
+                       self._inputError)
+
+    def _inputError(self, f):
+        log.msg("Error in input stream for %r" % self.transport)
+        log.err(f)
+        self.transport.closeStdin()
+    
+    def outReceived(self, data):
+        self.outStream.write(data)
+
+    def errReceived(self, data):
+        self.errStream.write(data)
+
+    def outConnectionLost(self):
+        self.outStream.finish()
+
+    def errConnectionLost(self):
+        self.errStream.finish()
+    
+    def processEnded(self, reason):
+        self.resultDeferred.errback(reason)
+        del self.resultDeferred
+
+
+class ProcessStreamer(object):
+    """Runs a process hooked up to streams.
+
+    Requires an input stream, has attributes 'outStream' and 'errStream'
+    for stdout and stderr.
+
+    outStream and errStream are public attributes providing streams
+    for stdout and stderr of the process.
+    """
+
+    def __init__(self, inputStream, program, args, env={}):
+        self.outStream = ProducerStream()
+        self.errStream = ProducerStream()
+        self._protocol = _ProcessStreamerProtocol(IByteStream(inputStream), self.outStream, self.errStream)
+        self._program = program
+        self._args = args
+        self._env = env
+    
+    def run(self):
+        """Run the process.
+
+        Returns Deferred which will eventually have errback for non-clean (exit code > 0)
+        exit, with ProcessTerminated, or callback with None on exit code 0.
+        """
+        # XXX what happens if spawn fails?
+        reactor.spawnProcess(self._protocol, self._program, self._args, env=self._env)
+        del self._env
+        return self._protocol.resultDeferred.addErrback(lambda _: _.trap(ti_error.ProcessDone))
+
+##############################
+####   generatorToStream  ####
+##############################
+
+class _StreamIterator(object):
+    done=False
+
+    def __iter__(self):
+        return self
+    def next(self):
+        if self.done:
+            raise StopIteration
+        return self.value
+    wait=object()
+
+class _IteratorStream(object):
+    length = None
+    
+    def __init__(self, fun, stream, args, kwargs):
+        self._stream=stream
+        self._streamIterator = _StreamIterator()
+        self._gen = fun(self._streamIterator, *args, **kwargs)
+        
+    def read(self):
+        try:
+            val = self._gen.next()
+        except StopIteration:
+            return None
+        else:
+            if val is _StreamIterator.wait:
+                newdata = self._stream.read()
+                if isinstance(newdata, defer.Deferred):
+                    return newdata.addCallback(self._gotRead)
+                else:
+                    return self._gotRead(newdata)
+            return val
+        
+    def _gotRead(self, data):
+        if data is None:
+            self._streamIterator.done=True
+        else:
+            self._streamIterator.value=data
+        return self.read()
+
+    def close(self):
+        self._stream.close()
+        del self._gen, self._stream, self._streamIterator
+
+    def split(self):
+        return fallbackSplit(self)
+    
+def generatorToStream(fun):
+    """Converts a generator function into a stream.
+    
+    The function should take an iterator as its first argument,
+    which will be converted *from* a stream by this wrapper, and
+    yield items which are turned *into* the results from the
+    stream's 'read' call.
+    
+    One important point: before every call to input.next(), you
+    *MUST* do a "yield input.wait" first. Yielding this magic value
+    takes care of ensuring that the input is not a deferred before
+    you see it.
+    
+    >>> from twext.web2 import stream
+    >>> from string import maketrans
+    >>> alphabet = 'abcdefghijklmnopqrstuvwxyz'
+    >>>
+    >>> def encrypt(input, key):
+    ...     code = alphabet[key:] + alphabet[:key]
+    ...     translator = maketrans(alphabet+alphabet.upper(), code+code.upper())
+    ...     yield input.wait
+    ...     for s in input:
+    ...         yield str(s).translate(translator)
+    ...         yield input.wait
+    ...
+    >>> encrypt = stream.generatorToStream(encrypt)
+    >>>
+    >>> plaintextStream = stream.MemoryStream('SampleSampleSample')
+    >>> encryptedStream = encrypt(plaintextStream, 13)
+    >>> encryptedStream.read()
+    'FnzcyrFnzcyrFnzcyr'
+    >>>
+    >>> plaintextStream = stream.MemoryStream('SampleSampleSample')
+    >>> encryptedStream = encrypt(plaintextStream, 13)
+    >>> evenMoreEncryptedStream = encrypt(encryptedStream, 13)
+    >>> evenMoreEncryptedStream.read()
+    'SampleSampleSample'
+    
+    """
+    def generatorToStream_inner(stream, *args, **kwargs):
+        return _IteratorStream(fun, stream, args, kwargs)
+    return generatorToStream_inner
+
+
+##############################
+####    BufferedStream    ####
+##############################
+
+class BufferedStream(object):
+    """A stream which buffers its data to provide operations like
+    readline and readExactly."""
+    
+    data = ""
+    def __init__(self, stream):
+        self.stream = stream
+
+    def _readUntil(self, f):
+        """Internal helper function which repeatedly calls f each time
+        after more data has been received, until it returns non-None."""
+        while True:
+            r = f()
+            if r is not None:
+                yield r; return
+            
+            newdata = self.stream.read()
+            if isinstance(newdata, defer.Deferred):
+                newdata = defer.waitForDeferred(newdata)
+                yield newdata; newdata = newdata.getResult()
+            
+            if newdata is None:
+                # End Of File
+                newdata = self.data
+                self.data = ''
+                yield newdata; return
+            self.data += str(newdata)
+    _readUntil = defer.deferredGenerator(_readUntil)
+
+    def readExactly(self, size=None):
+        """Read exactly size bytes of data, or, if size is None, read
+        the entire stream into a string."""
+        if size is not None and size < 0:
+            raise ValueError("readExactly: size cannot be negative: %s", size)
+        
+        def gotdata():
+            data = self.data
+            if size is not None and len(data) >= size:
+                pre,post = data[:size], data[size:]
+                self.data = post
+                return pre
+        return self._readUntil(gotdata)
+    
+        
+    def readline(self, delimiter='\r\n', size=None):
+        """
+        Read a line of data from the string, bounded by
+        delimiter. The delimiter is included in the return value.
+
+        If size is specified, read and return at most that many bytes,
+        even if the delimiter has not yet been reached. If the size
+        limit falls within a delimiter, the rest of the delimiter, and
+        the next line will be returned together.
+        """
+        if size is not None and size < 0:
+            raise ValueError("readline: size cannot be negative: %s" % (size, ))
+
+        def gotdata():
+            data = self.data
+            if size is not None:
+                splitpoint = data.find(delimiter, 0, size)
+                if splitpoint == -1:
+                    if len(data) >= size:
+                        splitpoint = size
+                else:
+                    splitpoint += len(delimiter)
+            else:
+                splitpoint = data.find(delimiter)
+                if splitpoint != -1:
+                    splitpoint += len(delimiter)
+            
+            if splitpoint != -1:
+                pre = data[:splitpoint]
+                self.data = data[splitpoint:]
+                return pre
+        return self._readUntil(gotdata)
+    
+    def pushback(self, pushed):
+        """Push data back into the buffer."""
+        
+        self.data = pushed + self.data
+        
+    def read(self):
+        data = self.data
+        if data:
+            self.data = ""
+            return data
+        return self.stream.read()
+
+    def _len(self):
+        l = self.stream.length
+        if l is None:
+            return None
+        return l + len(self.data)
+    
+    length = property(_len)
+    
+    def split(self, offset):
+        off = offset - len(self.data)
+        
+        pre, post = self.stream.split(max(0, off))
+        pre = BufferedStream(pre)
+        post = BufferedStream(post)
+        if off < 0:
+            pre.data = self.data[:-off]
+            post.data = self.data[-off:]
+        else:
+            pre.data = self.data
+        
+        return pre, post
+
+        
+def substream(stream, start, end):
+    if start > end:
+        raise ValueError("start position must be less than end position %r"
+                         % ((start, end),))
+    stream = stream.split(start)[1]
+    return stream.split(end - start)[0]
+
+
+
+__all__ = ['IStream', 'IByteStream', 'FileStream', 'MemoryStream', 'CompoundStream',
+           'readAndDiscard', 'fallbackSplit', 'ProducerStream', 'StreamProducer',
+           'BufferedStream', 'readStream', 'ProcessStreamer', 'readIntoFile',
+           'generatorToStream']
+

Deleted: CalendarServer/trunk/twext/web2/test/__init__.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/__init__.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/test/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,9 +0,0 @@
-# Copyright (c) 2001-2006 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-
-twext.web2.test: unittests for the Twext.Web2, Web Server Framework
-
-"""
-

Copied: CalendarServer/trunk/twext/web2/test/__init__.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/__init__.py)
===================================================================
--- CalendarServer/trunk/twext/web2/test/__init__.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/test/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,9 @@
+# Copyright (c) 2001-2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+
+twext.web2.test: unittests for the Twext.Web2, Web Server Framework
+
+"""
+

Deleted: CalendarServer/trunk/twext/web2/test/server.pem
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/server.pem	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/test/server.pem	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,36 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDBjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER
-MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
-ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
-cHNAcG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzEL
-MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhv
-c3QxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEB
-BQADSwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
-5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQC
-MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl
-MB0GA1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7
-hyNp65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoT
-CE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlw
-dG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3Qx
-LmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6
-BoJuVwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++
-7QGG/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JE
-WUQ9Ho4EzbYCOQ==
------END CERTIFICATE-----
------BEGIN RSA PRIVATE KEY-----
-MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
-5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAQJBAIqm/bz4NA1H++Vx5Ewx
-OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT
-ZIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4
-nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2
-HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNEH+vRWsAYU/gbx+OQB+7VOcBAiEA
-oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w=
------END RSA PRIVATE KEY-----
------BEGIN CERTIFICATE REQUEST-----
-MIIBDTCBuAIBADBTMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xEjAQ
-BgNVBAMTCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20w
-XDANBgkqhkiG9w0BAQEFAANLADBIAkEArL57d26W9fNXvOhNlZzlPOACmvwOZ5Ad
-NgLzJ1/MfsQQJ7hHVeHmTAjM664V+fXvwUGJLziCeBo1ysWLRnl8CQIDAQABoAAw
-DQYJKoZIhvcNAQEEBQADQQA7uqbrNTjVWpF6By5ZNPvhZ4YdFgkeXFVWi5ao/TaP
-Vq4BG021fJ9nlHRtr4rotpgHDX1rr+iWeHKsx4+5DRSy
------END CERTIFICATE REQUEST-----

Copied: CalendarServer/trunk/twext/web2/test/server.pem (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/server.pem)
===================================================================
--- CalendarServer/trunk/twext/web2/test/server.pem	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/test/server.pem	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzEL
+MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhv
+c3QxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEB
+BQADSwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
+5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQC
+MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl
+MB0GA1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7
+hyNp65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoT
+CE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlw
+dG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3Qx
+LmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6
+BoJuVwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++
+7QGG/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JE
+WUQ9Ho4EzbYCOQ==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
+5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAQJBAIqm/bz4NA1H++Vx5Ewx
+OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT
+ZIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4
+nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2
+HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNEH+vRWsAYU/gbx+OQB+7VOcBAiEA
+oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBDTCBuAIBADBTMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xEjAQ
+BgNVBAMTCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20w
+XDANBgkqhkiG9w0BAQEFAANLADBIAkEArL57d26W9fNXvOhNlZzlPOACmvwOZ5Ad
+NgLzJ1/MfsQQJ7hHVeHmTAjM664V+fXvwUGJLziCeBo1ysWLRnl8CQIDAQABoAAw
+DQYJKoZIhvcNAQEEBQADQQA7uqbrNTjVWpF6By5ZNPvhZ4YdFgkeXFVWi5ao/TaP
+Vq4BG021fJ9nlHRtr4rotpgHDX1rr+iWeHKsx4+5DRSy
+-----END CERTIFICATE REQUEST-----

Deleted: CalendarServer/trunk/twext/web2/test/simple_client.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/simple_client.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/test/simple_client.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,43 +0,0 @@
-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("GET /error HTTP/1.0\r\n")
-send("Host: localhost\r\n")
-
-if test_type == "lingeringClose":
-    print >> sys.stderr, ">> Sending lots of data"
-    send("Content-Length: 1000000\r\n\r\n")
-    send("X"*1000000)
-else:
-    send('\r\n')
-
-#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/twext/web2/test/simple_client.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/simple_client.py)
===================================================================
--- CalendarServer/trunk/twext/web2/test/simple_client.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/test/simple_client.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,43 @@
+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("GET /error HTTP/1.0\r\n")
+send("Host: localhost\r\n")
+
+if test_type == "lingeringClose":
+    print >> sys.stderr, ">> Sending lots of data"
+    send("Content-Length: 1000000\r\n\r\n")
+    send("X"*1000000)
+else:
+    send('\r\n')
+
+#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)

Deleted: CalendarServer/trunk/twext/web2/test/stream_data.txt
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/stream_data.txt	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/test/stream_data.txt	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1 +0,0 @@
-We've got some text!

Copied: CalendarServer/trunk/twext/web2/test/stream_data.txt (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/stream_data.txt)
===================================================================
--- CalendarServer/trunk/twext/web2/test/stream_data.txt	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/test/stream_data.txt	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1 @@
+We've got some text!

Deleted: CalendarServer/trunk/twext/web2/test/test_client.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_client.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/test/test_client.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,494 +0,0 @@
-# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Tests for HTTP client.
-"""
-
-from twisted.internet import protocol, defer
-
-from twext.web2.client import http
-from twext.web2 import http_headers
-from twext.web2 import stream
-from twext.web2.test.test_http import LoopbackRelay, HTTPTests, TestConnection
-
-
-
-class TestServer(protocol.Protocol):
-    data = ""
-    done = False
-
-    def dataReceived(self, data):
-        self.data += data
-
-    def write(self, data):
-        self.transport.write(data)
-
-    def connectionLost(self, reason):
-        self.done = True
-        self.transport.loseConnection()
-
-    def loseConnection(self):
-        self.done = True
-        self.transport.loseConnection()
-
-
-
-class ClientTests(HTTPTests):
-    def connect(self, logFile=None, maxPipeline=4,
-                inputTimeOut=60000, betweenRequestsTimeOut=600000):
-        cxn = TestConnection()
-
-        cxn.client = http.HTTPClientProtocol()
-        cxn.client.inputTimeOut = inputTimeOut
-        cxn.server = TestServer()
-
-        cxn.serverToClient = LoopbackRelay(cxn.client, logFile)
-        cxn.clientToServer = LoopbackRelay(cxn.server, logFile)
-
-        cxn.server.makeConnection(cxn.serverToClient)
-        cxn.client.makeConnection(cxn.clientToServer)
-
-        return cxn
-
-    def writeToClient(self, cxn, data):
-        cxn.server.write(data)
-        self.iterate(cxn)
-
-    def writeLines(self, cxn, lines):
-        self.writeToClient(cxn, '\r\n'.join(lines))
-
-    def assertReceived(self, cxn, expectedStatus, expectedHeaders,
-                       expectedContent=None):
-        self.iterate(cxn)
-
-        headers, content = cxn.server.data.split('\r\n\r\n', 1)
-        status, headers = headers.split('\r\n', 1)
-        headers = headers.split('\r\n')
-
-        # check status line
-        self.assertEquals(status, expectedStatus)
-
-        # check headers (header order isn't guraunteed so we use
-        # self.assertIn
-        for x in headers:
-            self.assertIn(x, expectedHeaders)
-
-        if not expectedContent:
-            expectedContent = ''
-
-        self.assertEquals(content, expectedContent)
-
-    def assertDone(self, cxn):
-        self.iterate(cxn)
-        self.assertEquals(cxn.server.done, True, 'Connection not closed.')
-
-    def assertHeaders(self, resp, expectedHeaders):
-        headers = list(resp.headers.getAllRawHeaders())
-        headers.sort()
-        self.assertEquals(headers, expectedHeaders)
-
-    def checkResponse(self, resp, code, headers, length, data):
-        """
-        Assert various things about a response: http code, headers, stream
-        length, and data in stream.
-        """
-        def gotData(gotdata):
-            self.assertEquals(gotdata, data)
-
-        self.assertEquals(resp.code, code)
-        self.assertHeaders(resp, headers)
-        self.assertEquals(resp.stream.length, length)
-
-        return defer.maybeDeferred(resp.stream.read).addCallback(gotData)
-
-
-
-class TestHTTPClient(ClientTests):
-    """
-    Test that the http client works.
-    """
-
-    def test_simpleRequest(self):
-        """
-        Your basic simple HTTP Request.
-        """
-        cxn = self.connect(inputTimeOut=None)
-        req = http.ClientRequest('GET', '/', None, None)
-
-        d = cxn.client.submitRequest(req).addCallback(self.checkResponse, 200, [], 10, '1234567890')
-
-        self.assertReceived(cxn, 'GET / HTTP/1.1',
-                                 ['Connection: close'])
-
-        self.writeLines(cxn, ('HTTP/1.1 200 OK',
-                              'Content-Length: 10',
-                              'Connection: close',
-                              '',
-                              '1234567890'))
-
-        return d.addCallback(lambda _: self.assertDone(cxn))
-
-    def test_delayedContent(self):
-        """
-        Make sure that the client returns the response object as soon as the
-        headers are received, even if the data hasn't arrived yet.
-        """
-
-        cxn = self.connect(inputTimeOut=None)
-        req = http.ClientRequest('GET', '/', None, None)
-
-        def gotData(data):
-            self.assertEquals(data, '1234567890')
-
-        def gotResp(resp):
-            self.assertEquals(resp.code, 200)
-            self.assertHeaders(resp, [])
-            self.assertEquals(resp.stream.length, 10)
-
-            self.writeToClient(cxn, '1234567890')
-
-            return defer.maybeDeferred(resp.stream.read).addCallback(gotData)
-
-        d = cxn.client.submitRequest(req).addCallback(gotResp)
-
-
-        self.assertReceived(cxn, 'GET / HTTP/1.1',
-                            ['Connection: close'])
-
-        self.writeLines(cxn, ('HTTP/1.1 200 OK',
-                              'Content-Length: 10',
-                              'Connection: close',
-                              '\r\n'))
-
-        return d.addCallback(lambda _: self.assertDone(cxn))
-
-    def test_prematurePipelining(self):
-        """
-        Ensure that submitting a second request before it's allowed results
-        in an AssertionError.
-        """
-        cxn = self.connect(inputTimeOut=None)
-        req = http.ClientRequest('GET', '/', None, None)
-
-        req2 = http.ClientRequest('GET', '/bar', None, None)
-
-        d = cxn.client.submitRequest(req, closeAfter=False).addCallback(
-            self.checkResponse, 200, [], 0, None)
-
-        self.assertRaises(AssertionError,
-                          cxn.client.submitRequest, req2)
-
-        self.assertReceived(cxn, 'GET / HTTP/1.1',
-                            ['Connection: Keep-Alive'])
-        self.writeLines(cxn, ('HTTP/1.1 200 OK',
-                              'Content-Length: 0',
-                              'Connection: close',
-                              '\r\n'))
-
-        return d
-
-    def test_userHeaders(self):
-        """
-        Make sure that headers get through in both directions.
-        """
-
-        cxn = self.connect(inputTimeOut=None)
-
-        def submitNext(_):
-            headers = http_headers.Headers(
-                headers={'Accept-Language': {'en': 1.0}},
-                rawHeaders={'X-My-Other-Header': ['socks']})
-
-            req = http.ClientRequest('GET', '/', headers, None)
-
-            cxn.server.data = ''
-
-            d = cxn.client.submitRequest(req, closeAfter=True)
-
-            self.assertReceived(cxn, 'GET / HTTP/1.1',
-                                ['Connection: close',
-                                 'X-My-Other-Header: socks',
-                                 'Accept-Language: en'])
-
-            self.writeLines(cxn, ('HTTP/1.1 200 OK',
-                                  'Content-Length: 0',
-                                  'Connection: close',
-                                  '\r\n'))
-
-            return d
-
-        req = http.ClientRequest('GET', '/',
-                                 {'Accept-Language': {'en': 1.0}}, None)
-
-        d = cxn.client.submitRequest(req, closeAfter=False).addCallback(
-            self.checkResponse, 200, [('X-Foobar', ['Yes'])], 0, None).addCallback(
-            submitNext)
-
-        self.assertReceived(cxn, 'GET / HTTP/1.1',
-                            ['Connection: Keep-Alive',
-                             'Accept-Language: en'])
-
-        self.writeLines(cxn, ('HTTP/1.1 200 OK',
-                              'Content-Length: 0',
-                              'X-Foobar: Yes',
-                              '\r\n'))
-
-        return d.addCallback(lambda _: self.assertDone(cxn))
-
-    def test_streamedUpload(self):
-        """
-        Make sure that sending request content works.
-        """
-
-        cxn = self.connect(inputTimeOut=None)
-
-        req = http.ClientRequest('PUT', '/foo', None, 'Helloooo content')
-
-        d = cxn.client.submitRequest(req).addCallback(self.checkResponse, 202, [], 0, None)
-
-        self.assertReceived(cxn, 'PUT /foo HTTP/1.1',
-                            ['Connection: close',
-                             'Content-Length: 16'],
-                            'Helloooo content')
-
-        self.writeLines(cxn, ('HTTP/1.1 202 Accepted',
-                              'Content-Length: 0',
-                              'Connection: close',
-                              '\r\n'))
-
-        return d.addCallback(lambda _: self.assertDone(cxn))
-
-    def test_sentHead(self):
-        """
-        Ensure that HEAD requests work, and return Content-Length.
-        """
-
-        cxn = self.connect(inputTimeOut=None)
-
-        req = http.ClientRequest('HEAD', '/', None, None)
-
-        d = cxn.client.submitRequest(req).addCallback(self.checkResponse, 200, [('Content-Length', ['5'])], 0, None)
-
-        self.assertReceived(cxn, 'HEAD / HTTP/1.1',
-                            ['Connection: close'])
-
-        self.writeLines(cxn, ('HTTP/1.1 200 OK',
-                              'Connection: close',
-                              'Content-Length: 5',
-                              '',
-                              'Pants')) # bad server
-
-        return d.addCallback(lambda _: self.assertDone(cxn))
-
-    def test_sentHeadKeepAlive(self):
-        """
-        Ensure that keepalive works right after a HEAD request.
-        """
-
-        cxn = self.connect(inputTimeOut=None)
-
-        req = http.ClientRequest('HEAD', '/', None, None)
-
-        didIt = [0]
-
-        def gotData(data):
-            self.assertEquals(data, None)
-
-        def gotResp(resp):
-            self.assertEquals(resp.code, 200)
-            self.assertEquals(resp.stream.length, 0)
-            self.assertHeaders(resp, [])
-
-            return defer.maybeDeferred(resp.stream.read).addCallback(gotData)
-
-        def submitRequest(second):
-            if didIt[0]:
-                return
-            didIt[0] = second
-
-            if second:
-                keepAlive='close'
-            else:
-                keepAlive='Keep-Alive'
-
-            cxn.server.data = ''
-
-            d = cxn.client.submitRequest(req, closeAfter=second).addCallback(
-                self.checkResponse, 200, [('Content-Length', ['5'])], 0, None)
-
-            self.assertReceived(cxn, 'HEAD / HTTP/1.1',
-                                ['Connection: '+ keepAlive])
-
-            self.writeLines(cxn, ('HTTP/1.1 200 OK',
-                                  'Connection: '+ keepAlive,
-                                  'Content-Length: 5',
-                                  '\r\n'))
-
-            return d.addCallback(lambda _: submitRequest(1))
-
-        d = submitRequest(0)
-
-        return d.addCallback(lambda _: self.assertDone(cxn))
-
-    def test_chunkedUpload(self):
-        """
-        Ensure chunked data is correctly decoded on upload.
-        """
-
-        cxn = self.connect(inputTimeOut=None)
-
-        data = 'Foo bar baz bax'
-        s = stream.ProducerStream(length=None)
-        s.write(data)
-
-        req = http.ClientRequest('PUT', '/', None, s)
-
-        d = cxn.client.submitRequest(req)
-
-        s.finish()
-
-        self.assertReceived(cxn, 'PUT / HTTP/1.1',
-                            ['Connection: close',
-                             'Transfer-Encoding: chunked'],
-                            '%X\r\n%s\r\n0\r\n\r\n' % (len(data), data))
-
-        self.writeLines(cxn, ('HTTP/1.1 200 OK',
-                              'Connection: close',
-                              'Content-Length: 0',
-                              '\r\n'))
-
-        return d.addCallback(lambda _: self.assertDone(cxn))
-
-
-
-class TestEdgeCases(ClientTests):
-    def test_serverDoesntSendConnectionClose(self):
-        """
-        Check that a lost connection is treated as end of response, if we
-        requested connection: close, even if the server didn't respond with
-        connection: close.
-        """
-
-        cxn = self.connect(inputTimeOut=None)
-        req = http.ClientRequest('GET', '/', None, None)
-
-        d = cxn.client.submitRequest(req).addCallback(self.checkResponse, 200, [], None, 'Some Content')
-
-        self.assertReceived(cxn, 'GET / HTTP/1.1',
-                            ['Connection: close'])
-
-        self.writeLines(cxn, ('HTTP/1.1 200 OK',
-                              '',
-                              'Some Content'))
-
-        return d.addCallback(lambda _: self.assertDone(cxn))
-
-    def test_serverIsntHttp(self):
-        """
-        Check that an error is returned if the server doesn't talk HTTP.
-        """
-
-        cxn = self.connect(inputTimeOut=None)
-        req = http.ClientRequest('GET', '/', None, None)
-
-        def gotResp(r):
-            print r
-
-        d = cxn.client.submitRequest(req).addCallback(gotResp)
-
-        self.assertFailure(d, http.ProtocolError)
-
-        self.writeLines(cxn, ('HTTP-NG/1.1 200 OK',
-                              '\r\n'))
-
-
-    def test_newServer(self):
-        """
-        Check that an error is returned if the server is a new major version.
-        """
-
-        cxn = self.connect(inputTimeOut=None)
-        req = http.ClientRequest('GET', '/', None, None)
-
-        d = cxn.client.submitRequest(req)
-
-        self.assertFailure(d, http.ProtocolError)
-
-        self.writeLines(cxn, ('HTTP/2.3 200 OK',
-                              '\r\n'))
-
-
-    def test_shortStatus(self):
-        """
-        Check that an error is returned if the response line is invalid.
-        """
-
-        cxn = self.connect(inputTimeOut=None)
-        req = http.ClientRequest('GET', '/', None, None)
-
-        d = cxn.client.submitRequest(req)
-
-        self.assertFailure(d, http.ProtocolError)
-
-        self.writeLines(cxn, ('HTTP/1.1 200',
-                              '\r\n'))
-
-    def test_errorReadingRequestStream(self):
-        """
-        Ensure that stream errors are propagated to the response.
-        """
-
-        cxn = self.connect(inputTimeOut=None)
-
-        s = stream.ProducerStream()
-        s.write('Foo')
-
-        req = http.ClientRequest('GET', '/', None, s)
-
-        d = cxn.client.submitRequest(req)
-
-        s.finish(IOError('Test Error'))
-
-        return self.assertFailure(d, IOError)
-
-
-    def test_connectionLost(self):
-        """
-        Check that closing the connection is propagated to the response
-        deferred.
-        """
-        cxn = self.connect(inputTimeOut=None)
-        req = http.ClientRequest('GET', '/', None, None)
-
-        d = cxn.client.submitRequest(req)
-
-        self.assertReceived(cxn, 'GET / HTTP/1.1',
-                                 ['Connection: close'])
-        cxn.client.connectionLost(ValueError("foo"))
-        return self.assertFailure(d, ValueError)
-
-
-    def test_connectionLostAfterHeaders(self):
-        """
-        Test that closing the connection after headers are sent is propagated
-        to the response stream.
-        """
-        cxn = self.connect(inputTimeOut=None)
-        req = http.ClientRequest('GET', '/', None, None)
-
-        d = cxn.client.submitRequest(req)
-
-        self.assertReceived(cxn, 'GET / HTTP/1.1',
-                                 ['Connection: close'])
-
-        self.writeLines(cxn, ('HTTP/1.1 200 OK',
-                              'Content-Length: 10',
-                              'Connection: close',
-                              '\r\n'))
-        cxn.client.connectionLost(ValueError("foo"))
-        def cb(response):
-            return self.assertFailure(response.stream.read(), ValueError)
-        d.addCallback(cb)
-        return d
-

Copied: CalendarServer/trunk/twext/web2/test/test_client.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_client.py)
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_client.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/test/test_client.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,494 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for HTTP client.
+"""
+
+from twisted.internet import protocol, defer
+
+from twext.web2.client import http
+from twext.web2 import http_headers
+from twext.web2 import stream
+from twext.web2.test.test_http import LoopbackRelay, HTTPTests, TestConnection
+
+
+
+class TestServer(protocol.Protocol):
+    data = ""
+    done = False
+
+    def dataReceived(self, data):
+        self.data += data
+
+    def write(self, data):
+        self.transport.write(data)
+
+    def connectionLost(self, reason):
+        self.done = True
+        self.transport.loseConnection()
+
+    def loseConnection(self):
+        self.done = True
+        self.transport.loseConnection()
+
+
+
+class ClientTests(HTTPTests):
+    def connect(self, logFile=None, maxPipeline=4,
+                inputTimeOut=60000, betweenRequestsTimeOut=600000):
+        cxn = TestConnection()
+
+        cxn.client = http.HTTPClientProtocol()
+        cxn.client.inputTimeOut = inputTimeOut
+        cxn.server = TestServer()
+
+        cxn.serverToClient = LoopbackRelay(cxn.client, logFile)
+        cxn.clientToServer = LoopbackRelay(cxn.server, logFile)
+
+        cxn.server.makeConnection(cxn.serverToClient)
+        cxn.client.makeConnection(cxn.clientToServer)
+
+        return cxn
+
+    def writeToClient(self, cxn, data):
+        cxn.server.write(data)
+        self.iterate(cxn)
+
+    def writeLines(self, cxn, lines):
+        self.writeToClient(cxn, '\r\n'.join(lines))
+
+    def assertReceived(self, cxn, expectedStatus, expectedHeaders,
+                       expectedContent=None):
+        self.iterate(cxn)
+
+        headers, content = cxn.server.data.split('\r\n\r\n', 1)
+        status, headers = headers.split('\r\n', 1)
+        headers = headers.split('\r\n')
+
+        # check status line
+        self.assertEquals(status, expectedStatus)
+
+        # check headers (header order isn't guraunteed so we use
+        # self.assertIn
+        for x in headers:
+            self.assertIn(x, expectedHeaders)
+
+        if not expectedContent:
+            expectedContent = ''
+
+        self.assertEquals(content, expectedContent)
+
+    def assertDone(self, cxn):
+        self.iterate(cxn)
+        self.assertEquals(cxn.server.done, True, 'Connection not closed.')
+
+    def assertHeaders(self, resp, expectedHeaders):
+        headers = list(resp.headers.getAllRawHeaders())
+        headers.sort()
+        self.assertEquals(headers, expectedHeaders)
+
+    def checkResponse(self, resp, code, headers, length, data):
+        """
+        Assert various things about a response: http code, headers, stream
+        length, and data in stream.
+        """
+        def gotData(gotdata):
+            self.assertEquals(gotdata, data)
+
+        self.assertEquals(resp.code, code)
+        self.assertHeaders(resp, headers)
+        self.assertEquals(resp.stream.length, length)
+
+        return defer.maybeDeferred(resp.stream.read).addCallback(gotData)
+
+
+
+class TestHTTPClient(ClientTests):
+    """
+    Test that the http client works.
+    """
+
+    def test_simpleRequest(self):
+        """
+        Your basic simple HTTP Request.
+        """
+        cxn = self.connect(inputTimeOut=None)
+        req = http.ClientRequest('GET', '/', None, None)
+
+        d = cxn.client.submitRequest(req).addCallback(self.checkResponse, 200, [], 10, '1234567890')
+
+        self.assertReceived(cxn, 'GET / HTTP/1.1',
+                                 ['Connection: close'])
+
+        self.writeLines(cxn, ('HTTP/1.1 200 OK',
+                              'Content-Length: 10',
+                              'Connection: close',
+                              '',
+                              '1234567890'))
+
+        return d.addCallback(lambda _: self.assertDone(cxn))
+
+    def test_delayedContent(self):
+        """
+        Make sure that the client returns the response object as soon as the
+        headers are received, even if the data hasn't arrived yet.
+        """
+
+        cxn = self.connect(inputTimeOut=None)
+        req = http.ClientRequest('GET', '/', None, None)
+
+        def gotData(data):
+            self.assertEquals(data, '1234567890')
+
+        def gotResp(resp):
+            self.assertEquals(resp.code, 200)
+            self.assertHeaders(resp, [])
+            self.assertEquals(resp.stream.length, 10)
+
+            self.writeToClient(cxn, '1234567890')
+
+            return defer.maybeDeferred(resp.stream.read).addCallback(gotData)
+
+        d = cxn.client.submitRequest(req).addCallback(gotResp)
+
+
+        self.assertReceived(cxn, 'GET / HTTP/1.1',
+                            ['Connection: close'])
+
+        self.writeLines(cxn, ('HTTP/1.1 200 OK',
+                              'Content-Length: 10',
+                              'Connection: close',
+                              '\r\n'))
+
+        return d.addCallback(lambda _: self.assertDone(cxn))
+
+    def test_prematurePipelining(self):
+        """
+        Ensure that submitting a second request before it's allowed results
+        in an AssertionError.
+        """
+        cxn = self.connect(inputTimeOut=None)
+        req = http.ClientRequest('GET', '/', None, None)
+
+        req2 = http.ClientRequest('GET', '/bar', None, None)
+
+        d = cxn.client.submitRequest(req, closeAfter=False).addCallback(
+            self.checkResponse, 200, [], 0, None)
+
+        self.assertRaises(AssertionError,
+                          cxn.client.submitRequest, req2)
+
+        self.assertReceived(cxn, 'GET / HTTP/1.1',
+                            ['Connection: Keep-Alive'])
+        self.writeLines(cxn, ('HTTP/1.1 200 OK',
+                              'Content-Length: 0',
+                              'Connection: close',
+                              '\r\n'))
+
+        return d
+
+    def test_userHeaders(self):
+        """
+        Make sure that headers get through in both directions.
+        """
+
+        cxn = self.connect(inputTimeOut=None)
+
+        def submitNext(_):
+            headers = http_headers.Headers(
+                headers={'Accept-Language': {'en': 1.0}},
+                rawHeaders={'X-My-Other-Header': ['socks']})
+
+            req = http.ClientRequest('GET', '/', headers, None)
+
+            cxn.server.data = ''
+
+            d = cxn.client.submitRequest(req, closeAfter=True)
+
+            self.assertReceived(cxn, 'GET / HTTP/1.1',
+                                ['Connection: close',
+                                 'X-My-Other-Header: socks',
+                                 'Accept-Language: en'])
+
+            self.writeLines(cxn, ('HTTP/1.1 200 OK',
+                                  'Content-Length: 0',
+                                  'Connection: close',
+                                  '\r\n'))
+
+            return d
+
+        req = http.ClientRequest('GET', '/',
+                                 {'Accept-Language': {'en': 1.0}}, None)
+
+        d = cxn.client.submitRequest(req, closeAfter=False).addCallback(
+            self.checkResponse, 200, [('X-Foobar', ['Yes'])], 0, None).addCallback(
+            submitNext)
+
+        self.assertReceived(cxn, 'GET / HTTP/1.1',
+                            ['Connection: Keep-Alive',
+                             'Accept-Language: en'])
+
+        self.writeLines(cxn, ('HTTP/1.1 200 OK',
+                              'Content-Length: 0',
+                              'X-Foobar: Yes',
+                              '\r\n'))
+
+        return d.addCallback(lambda _: self.assertDone(cxn))
+
+    def test_streamedUpload(self):
+        """
+        Make sure that sending request content works.
+        """
+
+        cxn = self.connect(inputTimeOut=None)
+
+        req = http.ClientRequest('PUT', '/foo', None, 'Helloooo content')
+
+        d = cxn.client.submitRequest(req).addCallback(self.checkResponse, 202, [], 0, None)
+
+        self.assertReceived(cxn, 'PUT /foo HTTP/1.1',
+                            ['Connection: close',
+                             'Content-Length: 16'],
+                            'Helloooo content')
+
+        self.writeLines(cxn, ('HTTP/1.1 202 Accepted',
+                              'Content-Length: 0',
+                              'Connection: close',
+                              '\r\n'))
+
+        return d.addCallback(lambda _: self.assertDone(cxn))
+
+    def test_sentHead(self):
+        """
+        Ensure that HEAD requests work, and return Content-Length.
+        """
+
+        cxn = self.connect(inputTimeOut=None)
+
+        req = http.ClientRequest('HEAD', '/', None, None)
+
+        d = cxn.client.submitRequest(req).addCallback(self.checkResponse, 200, [('Content-Length', ['5'])], 0, None)
+
+        self.assertReceived(cxn, 'HEAD / HTTP/1.1',
+                            ['Connection: close'])
+
+        self.writeLines(cxn, ('HTTP/1.1 200 OK',
+                              'Connection: close',
+                              'Content-Length: 5',
+                              '',
+                              'Pants')) # bad server
+
+        return d.addCallback(lambda _: self.assertDone(cxn))
+
+    def test_sentHeadKeepAlive(self):
+        """
+        Ensure that keepalive works right after a HEAD request.
+        """
+
+        cxn = self.connect(inputTimeOut=None)
+
+        req = http.ClientRequest('HEAD', '/', None, None)
+
+        didIt = [0]
+
+        def gotData(data):
+            self.assertEquals(data, None)
+
+        def gotResp(resp):
+            self.assertEquals(resp.code, 200)
+            self.assertEquals(resp.stream.length, 0)
+            self.assertHeaders(resp, [])
+
+            return defer.maybeDeferred(resp.stream.read).addCallback(gotData)
+
+        def submitRequest(second):
+            if didIt[0]:
+                return
+            didIt[0] = second
+
+            if second:
+                keepAlive='close'
+            else:
+                keepAlive='Keep-Alive'
+
+            cxn.server.data = ''
+
+            d = cxn.client.submitRequest(req, closeAfter=second).addCallback(
+                self.checkResponse, 200, [('Content-Length', ['5'])], 0, None)
+
+            self.assertReceived(cxn, 'HEAD / HTTP/1.1',
+                                ['Connection: '+ keepAlive])
+
+            self.writeLines(cxn, ('HTTP/1.1 200 OK',
+                                  'Connection: '+ keepAlive,
+                                  'Content-Length: 5',
+                                  '\r\n'))
+
+            return d.addCallback(lambda _: submitRequest(1))
+
+        d = submitRequest(0)
+
+        return d.addCallback(lambda _: self.assertDone(cxn))
+
+    def test_chunkedUpload(self):
+        """
+        Ensure chunked data is correctly decoded on upload.
+        """
+
+        cxn = self.connect(inputTimeOut=None)
+
+        data = 'Foo bar baz bax'
+        s = stream.ProducerStream(length=None)
+        s.write(data)
+
+        req = http.ClientRequest('PUT', '/', None, s)
+
+        d = cxn.client.submitRequest(req)
+
+        s.finish()
+
+        self.assertReceived(cxn, 'PUT / HTTP/1.1',
+                            ['Connection: close',
+                             'Transfer-Encoding: chunked'],
+                            '%X\r\n%s\r\n0\r\n\r\n' % (len(data), data))
+
+        self.writeLines(cxn, ('HTTP/1.1 200 OK',
+                              'Connection: close',
+                              'Content-Length: 0',
+                              '\r\n'))
+
+        return d.addCallback(lambda _: self.assertDone(cxn))
+
+
+
+class TestEdgeCases(ClientTests):
+    def test_serverDoesntSendConnectionClose(self):
+        """
+        Check that a lost connection is treated as end of response, if we
+        requested connection: close, even if the server didn't respond with
+        connection: close.
+        """
+
+        cxn = self.connect(inputTimeOut=None)
+        req = http.ClientRequest('GET', '/', None, None)
+
+        d = cxn.client.submitRequest(req).addCallback(self.checkResponse, 200, [], None, 'Some Content')
+
+        self.assertReceived(cxn, 'GET / HTTP/1.1',
+                            ['Connection: close'])
+
+        self.writeLines(cxn, ('HTTP/1.1 200 OK',
+                              '',
+                              'Some Content'))
+
+        return d.addCallback(lambda _: self.assertDone(cxn))
+
+    def test_serverIsntHttp(self):
+        """
+        Check that an error is returned if the server doesn't talk HTTP.
+        """
+
+        cxn = self.connect(inputTimeOut=None)
+        req = http.ClientRequest('GET', '/', None, None)
+
+        def gotResp(r):
+            print r
+
+        d = cxn.client.submitRequest(req).addCallback(gotResp)
+
+        self.assertFailure(d, http.ProtocolError)
+
+        self.writeLines(cxn, ('HTTP-NG/1.1 200 OK',
+                              '\r\n'))
+
+
+    def test_newServer(self):
+        """
+        Check that an error is returned if the server is a new major version.
+        """
+
+        cxn = self.connect(inputTimeOut=None)
+        req = http.ClientRequest('GET', '/', None, None)
+
+        d = cxn.client.submitRequest(req)
+
+        self.assertFailure(d, http.ProtocolError)
+
+        self.writeLines(cxn, ('HTTP/2.3 200 OK',
+                              '\r\n'))
+
+
+    def test_shortStatus(self):
+        """
+        Check that an error is returned if the response line is invalid.
+        """
+
+        cxn = self.connect(inputTimeOut=None)
+        req = http.ClientRequest('GET', '/', None, None)
+
+        d = cxn.client.submitRequest(req)
+
+        self.assertFailure(d, http.ProtocolError)
+
+        self.writeLines(cxn, ('HTTP/1.1 200',
+                              '\r\n'))
+
+    def test_errorReadingRequestStream(self):
+        """
+        Ensure that stream errors are propagated to the response.
+        """
+
+        cxn = self.connect(inputTimeOut=None)
+
+        s = stream.ProducerStream()
+        s.write('Foo')
+
+        req = http.ClientRequest('GET', '/', None, s)
+
+        d = cxn.client.submitRequest(req)
+
+        s.finish(IOError('Test Error'))
+
+        return self.assertFailure(d, IOError)
+
+
+    def test_connectionLost(self):
+        """
+        Check that closing the connection is propagated to the response
+        deferred.
+        """
+        cxn = self.connect(inputTimeOut=None)
+        req = http.ClientRequest('GET', '/', None, None)
+
+        d = cxn.client.submitRequest(req)
+
+        self.assertReceived(cxn, 'GET / HTTP/1.1',
+                                 ['Connection: close'])
+        cxn.client.connectionLost(ValueError("foo"))
+        return self.assertFailure(d, ValueError)
+
+
+    def test_connectionLostAfterHeaders(self):
+        """
+        Test that closing the connection after headers are sent is propagated
+        to the response stream.
+        """
+        cxn = self.connect(inputTimeOut=None)
+        req = http.ClientRequest('GET', '/', None, None)
+
+        d = cxn.client.submitRequest(req)
+
+        self.assertReceived(cxn, 'GET / HTTP/1.1',
+                                 ['Connection: close'])
+
+        self.writeLines(cxn, ('HTTP/1.1 200 OK',
+                              'Content-Length: 10',
+                              'Connection: close',
+                              '\r\n'))
+        cxn.client.connectionLost(ValueError("foo"))
+        def cb(response):
+            return self.assertFailure(response.stream.read(), ValueError)
+        d.addCallback(cb)
+        return d
+

Deleted: CalendarServer/trunk/twext/web2/test/test_compat.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_compat.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/test/test_compat.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,58 +0,0 @@
-from twext.web2.test.test_server import BaseCase
-import sys
-
-try:
-    from twisted.web import resource
-
-    class OldWebResource(resource.Resource):
-        def __init__(self, message, *args, **kwargs):
-            self.message = message
-            resource.Resource.__init__(self, *args, **kwargs)
-            
-        isLeaf = True
-        
-        def render(self, req):
-            return self.message
-    
-except ImportError:
-    resource = None
-
-class OldWebCompat(BaseCase):
-    try:
-        import twisted.web
-    except ImportError:
-        skip = "can't run w/o twisted.web"
-
-    def testOldWebResource(self):
-        ow = OldWebResource('I am an OldWebResource')
-        
-        self.assertResponse((ow, "http://localhost/"),
-                            (200, {}, 'I am an OldWebResource'))
-
-    def testOldWebResourceNotLeaf(self):
-        ow = OldWebResource('I am not a leaf')
-        ow.isLeaf = False
-
-        self.assertResponse((ow, "http://localhost/"),
-                            (200, {}, 'I am not a leaf'))
-
-    def testOldWebResourceWithChildren(self):
-            
-        ow = OldWebResource('I am an OldWebResource with a child')
-        
-        ow.isLeaf = False
-
-        ow.putChild('child',
-                    OldWebResource('I am a child of an OldWebResource'))
-
-        self.assertResponse((ow, "http://localhost/"),
-                            (200, {},
-                             'I am an OldWebResource with a child'))
-
-        self.assertResponse((ow, "http://localhost/child"),
-                            (200, {},
-                             'I am a child of an OldWebResource'))
-
-        
-if not resource:
-    OldWebCompat.skip = "can't run w/o twisted.web"

Copied: CalendarServer/trunk/twext/web2/test/test_compat.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_compat.py)
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_compat.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/test/test_compat.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,58 @@
+from twext.web2.test.test_server import BaseCase
+import sys
+
+try:
+    from twisted.web import resource
+
+    class OldWebResource(resource.Resource):
+        def __init__(self, message, *args, **kwargs):
+            self.message = message
+            resource.Resource.__init__(self, *args, **kwargs)
+            
+        isLeaf = True
+        
+        def render(self, req):
+            return self.message
+    
+except ImportError:
+    resource = None
+
+class OldWebCompat(BaseCase):
+    try:
+        import twisted.web
+    except ImportError:
+        skip = "can't run w/o twisted.web"
+
+    def testOldWebResource(self):
+        ow = OldWebResource('I am an OldWebResource')
+        
+        self.assertResponse((ow, "http://localhost/"),
+                            (200, {}, 'I am an OldWebResource'))
+
+    def testOldWebResourceNotLeaf(self):
+        ow = OldWebResource('I am not a leaf')
+        ow.isLeaf = False
+
+        self.assertResponse((ow, "http://localhost/"),
+                            (200, {}, 'I am not a leaf'))
+
+    def testOldWebResourceWithChildren(self):
+            
+        ow = OldWebResource('I am an OldWebResource with a child')
+        
+        ow.isLeaf = False
+
+        ow.putChild('child',
+                    OldWebResource('I am a child of an OldWebResource'))
+
+        self.assertResponse((ow, "http://localhost/"),
+                            (200, {},
+                             'I am an OldWebResource with a child'))
+
+        self.assertResponse((ow, "http://localhost/child"),
+                            (200, {},
+                             'I am a child of an OldWebResource'))
+
+        
+if not resource:
+    OldWebCompat.skip = "can't run w/o twisted.web"

Deleted: CalendarServer/trunk/twext/web2/test/test_fileupload.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_fileupload.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/test/test_fileupload.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,290 +0,0 @@
-# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Tests for L{twext.web2.fileupload} and its different parsing functions.
-"""
-
-from twisted.internet import defer
-from twisted.trial import unittest
-from twisted.internet.defer import waitForDeferred, deferredGenerator
-
-from twext.web2 import stream, fileupload
-from twext.web2.http_headers import MimeType
-
-
-
-class TestStream(stream.SimpleStream):
-    """
-    A stream that reads less data at a time than it could.
-    """
-    def __init__(self, mem, maxReturn=1000, start=0, length=None):
-        self.mem = mem
-        self.start = start
-        self.maxReturn = maxReturn
-        if length is None:
-            self.length = len(mem) - start
-        else:
-            if len(mem) < length:
-                raise ValueError("len(mem) < start + length")
-            self.length = length
-
-    def read(self):
-        if self.mem is None:
-            return None
-        if self.length == 0:
-            result = None
-        else:
-            amtToRead = min(self.maxReturn, self.length)
-            result = buffer(self.mem, self.start, amtToRead)
-            self.length -= amtToRead
-            self.start += amtToRead
-        return result
-
-    def close(self):
-        self.mem = None
-        stream.SimpleStream.close(self)
-
-
-
-class MultipartTests(unittest.TestCase):
-    def doTestError(self, boundary, data, expected_error):
-        # Test different amounts of data at a time.
-        ds = [fileupload.parseMultipartFormData(TestStream(data,
-                                                           maxReturn=bytes),
-                                                boundary)
-              for bytes in range(1, 20)]
-        d = defer.DeferredList(ds, consumeErrors=True)
-        d.addCallback(self._assertFailures, expected_error)
-        return d
-
-    def _assertFailures(self, failures, *expectedFailures):
-        for flag, failure in failures:
-            self.failUnlessEqual(flag, defer.FAILURE)
-            failure.trap(*expectedFailures)
-
-    def doTest(self, boundary, data, expected_args, expected_files):
-        #import time, gc, cgi, cStringIO
-        for bytes in range(1, 20):
-            #s = TestStream(data, maxReturn=bytes)
-            s = stream.IStream(data)
-            #t=time.time()
-            d = waitForDeferred(fileupload.parseMultipartFormData(s, boundary))
-            yield d; args, files = d.getResult()
-            #e=time.time()
-            #print "%.2g"%(e-t)
-            self.assertEquals(args, expected_args)
-
-            # Read file data back into memory to compare.
-            out = {}
-            for name, l in files.items():
-                out[name] = [(filename, ctype, f.read()) for (filename, ctype, f) in l]
-            self.assertEquals(out, expected_files)
-
-        #data=cStringIO.StringIO(data)
-        #t=time.time()
-        #d=cgi.parse_multipart(data, {'boundary':boundary})
-        #e=time.time()
-        #print "CGI: %.2g"%(e-t)
-    doTest = deferredGenerator(doTest)
-
-    def testNormalUpload(self):
-        return self.doTest(
-            '---------------------------155781040421463194511908194298',
-"""-----------------------------155781040421463194511908194298\r
-Content-Disposition: form-data; name="foo"\r
-\r
-Foo Bar\r
------------------------------155781040421463194511908194298\r
-Content-Disposition: form-data; name="file"; filename="filename"\r
-Content-Type: text/html\r
-\r
-Contents of a file
-blah
-blah\r
------------------------------155781040421463194511908194298--\r
-""",
-            {'foo':['Foo Bar']},
-            {'file':[('filename', MimeType('text', 'html'),
-                      "Contents of a file\nblah\nblah")]})
-
-    def testMultipleUpload(self):
-        return self.doTest(
-            'xyz',
-"""--xyz\r
-Content-Disposition: form-data; name="foo"\r
-\r
-Foo Bar\r
---xyz\r
-Content-Disposition: form-data; name="foo"\r
-\r
-Baz\r
---xyz\r
-Content-Disposition: form-data; name="file"; filename="filename"\r
-Content-Type: text/html\r
-\r
-blah\r
---xyz\r
-Content-Disposition: form-data; name="file"; filename="filename"\r
-Content-Type: text/plain\r
-\r
-bleh\r
---xyz--\r
-""",
-            {'foo':['Foo Bar', 'Baz']},
-            {'file':[('filename', MimeType('text', 'html'), "blah"),
-                     ('filename', MimeType('text', 'plain'), "bleh")]})
-
-
-    def testStupidFilename(self):
-        return self.doTest(
-            '----------0xKhTmLbOuNdArY',
-"""------------0xKhTmLbOuNdArY\r
-Content-Disposition: form-data; name="file"; filename="foo"; name="foobar.txt"\r
-Content-Type: text/plain\r
-\r
-Contents of a file
-blah
-blah\r
-------------0xKhTmLbOuNdArY--\r
-""",
-            {},
-            {'file':[('foo"; name="foobar.txt', MimeType('text', 'plain'),
-                      "Contents of a file\nblah\nblah")]})
-
-
-    def testEmptyFilename(self):
-        return self.doTest(
-            'curlPYafCMnsamUw9kSkJJkSen41sAV',
-"""--curlPYafCMnsamUw9kSkJJkSen41sAV\r
-cONTENT-tYPE: application/octet-stream\r
-cONTENT-dISPOSITION: FORM-DATA; NAME="foo"; FILENAME=""\r
-\r
-qwertyuiop\r
---curlPYafCMnsamUw9kSkJJkSen41sAV--\r
-""",
-            {},
-            {'foo':[('', MimeType('application', 'octet-stream'),
-                     "qwertyuiop")]})
-
-
-# Failing parses
-    def testMissingContentDisposition(self):
-        return self.doTestError(
-            '----------0xKhTmLbOuNdArY',
-"""------------0xKhTmLbOuNdArY\r
-Content-Type: text/html\r
-\r
-Blah blah I am a stupid webbrowser\r
-------------0xKhTmLbOuNdArY--\r
-""",
-            fileupload.MimeFormatError)
-
-
-    def testRandomData(self):
-        return self.doTestError(
-            'boundary',
-"""--sdkjsadjlfjlj skjsfdkljsd
-sfdkjsfdlkjhsfadklj sffkj""",
-            fileupload.MimeFormatError)
-
-
-    def test_tooBigUpload(self):
-        """
-        Test that a too big form post fails.
-        """
-        boundary = '---------------------------155781040421463194511908194298'
-        data = """-----------------------------155781040421463194511908194298\r
-Content-Disposition: form-data; name="foo"\r
-\r
-Foo Bar\r
------------------------------155781040421463194511908194298\r
-Content-Disposition: form-data; name="file"; filename="filename"\r
-Content-Type: text/html\r
-\r
-Contents of a file
-blah
-blah\r
------------------------------155781040421463194511908194298--\r
-"""
-        s = stream.IStream(data)
-        return self.assertFailure(
-            fileupload.parseMultipartFormData(s, boundary, maxSize=200),
-            fileupload.MimeFormatError)
-
-
-    def test_tooManyFields(self):
-        """
-        Test when breaking the maximum number of fields.
-        """
-        boundary = 'xyz'
-        data = """--xyz\r
-Content-Disposition: form-data; name="foo"\r
-\r
-Foo Bar\r
---xyz\r
-Content-Disposition: form-data; name="foo"\r
-\r
-Baz\r
---xyz\r
-Content-Disposition: form-data; name="file"; filename="filename"\r
-Content-Type: text/html\r
-\r
-blah\r
---xyz\r
-Content-Disposition: form-data; name="file"; filename="filename"\r
-Content-Type: text/plain\r
-\r
-bleh\r
---xyz--\r
-"""
-        s = stream.IStream(data)
-        return self.assertFailure(
-            fileupload.parseMultipartFormData(s, boundary, maxFields=3),
-            fileupload.MimeFormatError)
-
-
-    def test_maxMem(self):
-        """
-        An attachment with no filename goes to memory: check that the
-        C{maxMem} parameter limits the size of this kind of attachment.
-        """
-        boundary = '---------------------------155781040421463194511908194298'
-        data = """-----------------------------155781040421463194511908194298\r
-Content-Disposition: form-data; name="foo"\r
-\r
-Foo Bar and more content\r
------------------------------155781040421463194511908194298\r
-Content-Disposition: form-data; name="file"; filename="filename"\r
-Content-Type: text/html\r
-\r
-Contents of a file
-blah
-blah\r
------------------------------155781040421463194511908194298--\r
-"""
-        s = stream.IStream(data)
-        return self.assertFailure(
-            fileupload.parseMultipartFormData(s, boundary, maxMem=10),
-            fileupload.MimeFormatError)
-
-
-
-class TestURLEncoded(unittest.TestCase):
-    def doTest(self, data, expected_args):
-        for bytes in range(1, 20):
-            s = TestStream(data, maxReturn=bytes)
-            d = waitForDeferred(fileupload.parse_urlencoded(s))
-            yield d; args = d.getResult()
-            self.assertEquals(args, expected_args)
-    doTest = deferredGenerator(doTest)
-
-
-    def test_parseValid(self):
-        self.doTest("a=b&c=d&c=e", {'a':['b'], 'c':['d', 'e']})
-        self.doTest("a=b&c=d&c=e", {'a':['b'], 'c':['d', 'e']})
-        self.doTest("a=b+c%20d", {'a':['b c d']})
-
-
-    def test_parseInvalid(self):
-        self.doTest("a&b=c", {'b':['c']})

Copied: CalendarServer/trunk/twext/web2/test/test_fileupload.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_fileupload.py)
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_fileupload.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/test/test_fileupload.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,290 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twext.web2.fileupload} and its different parsing functions.
+"""
+
+from twisted.internet import defer
+from twisted.trial import unittest
+from twisted.internet.defer import waitForDeferred, deferredGenerator
+
+from twext.web2 import stream, fileupload
+from twext.web2.http_headers import MimeType
+
+
+
+class TestStream(stream.SimpleStream):
+    """
+    A stream that reads less data at a time than it could.
+    """
+    def __init__(self, mem, maxReturn=1000, start=0, length=None):
+        self.mem = mem
+        self.start = start
+        self.maxReturn = maxReturn
+        if length is None:
+            self.length = len(mem) - start
+        else:
+            if len(mem) < length:
+                raise ValueError("len(mem) < start + length")
+            self.length = length
+
+    def read(self):
+        if self.mem is None:
+            return None
+        if self.length == 0:
+            result = None
+        else:
+            amtToRead = min(self.maxReturn, self.length)
+            result = buffer(self.mem, self.start, amtToRead)
+            self.length -= amtToRead
+            self.start += amtToRead
+        return result
+
+    def close(self):
+        self.mem = None
+        stream.SimpleStream.close(self)
+
+
+
+class MultipartTests(unittest.TestCase):
+    def doTestError(self, boundary, data, expected_error):
+        # Test different amounts of data at a time.
+        ds = [fileupload.parseMultipartFormData(TestStream(data,
+                                                           maxReturn=bytes),
+                                                boundary)
+              for bytes in range(1, 20)]
+        d = defer.DeferredList(ds, consumeErrors=True)
+        d.addCallback(self._assertFailures, expected_error)
+        return d
+
+    def _assertFailures(self, failures, *expectedFailures):
+        for flag, failure in failures:
+            self.failUnlessEqual(flag, defer.FAILURE)
+            failure.trap(*expectedFailures)
+
+    def doTest(self, boundary, data, expected_args, expected_files):
+        #import time, gc, cgi, cStringIO
+        for bytes in range(1, 20):
+            #s = TestStream(data, maxReturn=bytes)
+            s = stream.IStream(data)
+            #t=time.time()
+            d = waitForDeferred(fileupload.parseMultipartFormData(s, boundary))
+            yield d; args, files = d.getResult()
+            #e=time.time()
+            #print "%.2g"%(e-t)
+            self.assertEquals(args, expected_args)
+
+            # Read file data back into memory to compare.
+            out = {}
+            for name, l in files.items():
+                out[name] = [(filename, ctype, f.read()) for (filename, ctype, f) in l]
+            self.assertEquals(out, expected_files)
+
+        #data=cStringIO.StringIO(data)
+        #t=time.time()
+        #d=cgi.parse_multipart(data, {'boundary':boundary})
+        #e=time.time()
+        #print "CGI: %.2g"%(e-t)
+    doTest = deferredGenerator(doTest)
+
+    def testNormalUpload(self):
+        return self.doTest(
+            '---------------------------155781040421463194511908194298',
+"""-----------------------------155781040421463194511908194298\r
+Content-Disposition: form-data; name="foo"\r
+\r
+Foo Bar\r
+-----------------------------155781040421463194511908194298\r
+Content-Disposition: form-data; name="file"; filename="filename"\r
+Content-Type: text/html\r
+\r
+Contents of a file
+blah
+blah\r
+-----------------------------155781040421463194511908194298--\r
+""",
+            {'foo':['Foo Bar']},
+            {'file':[('filename', MimeType('text', 'html'),
+                      "Contents of a file\nblah\nblah")]})
+
+    def testMultipleUpload(self):
+        return self.doTest(
+            'xyz',
+"""--xyz\r
+Content-Disposition: form-data; name="foo"\r
+\r
+Foo Bar\r
+--xyz\r
+Content-Disposition: form-data; name="foo"\r
+\r
+Baz\r
+--xyz\r
+Content-Disposition: form-data; name="file"; filename="filename"\r
+Content-Type: text/html\r
+\r
+blah\r
+--xyz\r
+Content-Disposition: form-data; name="file"; filename="filename"\r
+Content-Type: text/plain\r
+\r
+bleh\r
+--xyz--\r
+""",
+            {'foo':['Foo Bar', 'Baz']},
+            {'file':[('filename', MimeType('text', 'html'), "blah"),
+                     ('filename', MimeType('text', 'plain'), "bleh")]})
+
+
+    def testStupidFilename(self):
+        return self.doTest(
+            '----------0xKhTmLbOuNdArY',
+"""------------0xKhTmLbOuNdArY\r
+Content-Disposition: form-data; name="file"; filename="foo"; name="foobar.txt"\r
+Content-Type: text/plain\r
+\r
+Contents of a file
+blah
+blah\r
+------------0xKhTmLbOuNdArY--\r
+""",
+            {},
+            {'file':[('foo"; name="foobar.txt', MimeType('text', 'plain'),
+                      "Contents of a file\nblah\nblah")]})
+
+
+    def testEmptyFilename(self):
+        return self.doTest(
+            'curlPYafCMnsamUw9kSkJJkSen41sAV',
+"""--curlPYafCMnsamUw9kSkJJkSen41sAV\r
+cONTENT-tYPE: application/octet-stream\r
+cONTENT-dISPOSITION: FORM-DATA; NAME="foo"; FILENAME=""\r
+\r
+qwertyuiop\r
+--curlPYafCMnsamUw9kSkJJkSen41sAV--\r
+""",
+            {},
+            {'foo':[('', MimeType('application', 'octet-stream'),
+                     "qwertyuiop")]})
+
+
+# Failing parses
+    def testMissingContentDisposition(self):
+        return self.doTestError(
+            '----------0xKhTmLbOuNdArY',
+"""------------0xKhTmLbOuNdArY\r
+Content-Type: text/html\r
+\r
+Blah blah I am a stupid webbrowser\r
+------------0xKhTmLbOuNdArY--\r
+""",
+            fileupload.MimeFormatError)
+
+
+    def testRandomData(self):
+        return self.doTestError(
+            'boundary',
+"""--sdkjsadjlfjlj skjsfdkljsd
+sfdkjsfdlkjhsfadklj sffkj""",
+            fileupload.MimeFormatError)
+
+
+    def test_tooBigUpload(self):
+        """
+        Test that a too big form post fails.
+        """
+        boundary = '---------------------------155781040421463194511908194298'
+        data = """-----------------------------155781040421463194511908194298\r
+Content-Disposition: form-data; name="foo"\r
+\r
+Foo Bar\r
+-----------------------------155781040421463194511908194298\r
+Content-Disposition: form-data; name="file"; filename="filename"\r
+Content-Type: text/html\r
+\r
+Contents of a file
+blah
+blah\r
+-----------------------------155781040421463194511908194298--\r
+"""
+        s = stream.IStream(data)
+        return self.assertFailure(
+            fileupload.parseMultipartFormData(s, boundary, maxSize=200),
+            fileupload.MimeFormatError)
+
+
+    def test_tooManyFields(self):
+        """
+        Test when breaking the maximum number of fields.
+        """
+        boundary = 'xyz'
+        data = """--xyz\r
+Content-Disposition: form-data; name="foo"\r
+\r
+Foo Bar\r
+--xyz\r
+Content-Disposition: form-data; name="foo"\r
+\r
+Baz\r
+--xyz\r
+Content-Disposition: form-data; name="file"; filename="filename"\r
+Content-Type: text/html\r
+\r
+blah\r
+--xyz\r
+Content-Disposition: form-data; name="file"; filename="filename"\r
+Content-Type: text/plain\r
+\r
+bleh\r
+--xyz--\r
+"""
+        s = stream.IStream(data)
+        return self.assertFailure(
+            fileupload.parseMultipartFormData(s, boundary, maxFields=3),
+            fileupload.MimeFormatError)
+
+
+    def test_maxMem(self):
+        """
+        An attachment with no filename goes to memory: check that the
+        C{maxMem} parameter limits the size of this kind of attachment.
+        """
+        boundary = '---------------------------155781040421463194511908194298'
+        data = """-----------------------------155781040421463194511908194298\r
+Content-Disposition: form-data; name="foo"\r
+\r
+Foo Bar and more content\r
+-----------------------------155781040421463194511908194298\r
+Content-Disposition: form-data; name="file"; filename="filename"\r
+Content-Type: text/html\r
+\r
+Contents of a file
+blah
+blah\r
+-----------------------------155781040421463194511908194298--\r
+"""
+        s = stream.IStream(data)
+        return self.assertFailure(
+            fileupload.parseMultipartFormData(s, boundary, maxMem=10),
+            fileupload.MimeFormatError)
+
+
+
+class TestURLEncoded(unittest.TestCase):
+    def doTest(self, data, expected_args):
+        for bytes in range(1, 20):
+            s = TestStream(data, maxReturn=bytes)
+            d = waitForDeferred(fileupload.parse_urlencoded(s))
+            yield d; args = d.getResult()
+            self.assertEquals(args, expected_args)
+    doTest = deferredGenerator(doTest)
+
+
+    def test_parseValid(self):
+        self.doTest("a=b&c=d&c=e", {'a':['b'], 'c':['d', 'e']})
+        self.doTest("a=b&c=d&c=e", {'a':['b'], 'c':['d', 'e']})
+        self.doTest("a=b+c%20d", {'a':['b c d']})
+
+
+    def test_parseInvalid(self):
+        self.doTest("a&b=c", {'b':['c']})

Deleted: CalendarServer/trunk/twext/web2/test/test_http.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_http.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/test/test_http.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,1120 +0,0 @@
-
-from __future__ import nested_scopes
-
-import time, sys
-
-from zope.interface import implements
-
-from twisted.trial import unittest
-from twext.web2 import http, http_headers, responsecode, error, iweb, stream
-from twext.web2 import channel
-
-from twisted.internet import reactor, protocol, address, interfaces, utils
-from twisted.internet import defer
-from twisted.internet.defer import waitForDeferred, deferredGenerator
-from twisted.protocols import loopback
-from twisted.python import util, runtime
-from twisted.internet.task import deferLater
-
-class PreconditionTestCase(unittest.TestCase):
-    def checkPreconditions(self, request, response, expectedResult, expectedCode,
-                           **kw):
-        preconditionsPass = True
-
-        try:
-            http.checkPreconditions(request, response, **kw)
-        except http.HTTPError, e:
-            preconditionsPass = False
-            self.assertEquals(e.response.code, expectedCode)
-        self.assertEquals(preconditionsPass, expectedResult)
-
-    def testWithoutHeaders(self):
-        request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
-        out_headers = http_headers.Headers()
-        response = http.Response(responsecode.OK, out_headers, None)
-
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-        out_headers.setHeader("ETag", http_headers.ETag('foo'))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-        out_headers.removeHeader("ETag")
-        out_headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-        out_headers.setHeader("ETag", http_headers.ETag('foo'))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-    def testIfMatch(self):
-        request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
-        out_headers = http_headers.Headers()
-        response = http.Response(responsecode.OK, out_headers, None)
-
-        # Behavior with no ETag set, should be same as with an ETag
-        request.headers.setRawHeaders("If-Match", ('*',))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED, entityExists=False)
-
-        # Ask for tag, but no etag set.
-        request.headers.setRawHeaders("If-Match", ('"frob"',))
-        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
-
-        ## Actually set the ETag header
-        out_headers.setHeader("ETag", http_headers.ETag('foo'))
-        out_headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
-
-        # behavior of entityExists
-        request.headers.setRawHeaders("If-Match", ('*',))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED, entityExists=False)
-
-        # tag matches
-        request.headers.setRawHeaders("If-Match", ('"frob", "foo"',))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-        # none match
-        request.headers.setRawHeaders("If-Match", ('"baz", "bob"',))
-        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
-
-        # But if we have an error code already, ignore this header
-        response.code = responsecode.INTERNAL_SERVER_ERROR
-        self.checkPreconditions(request, response, True, responsecode.INTERNAL_SERVER_ERROR)
-        response.code = responsecode.OK
-
-        # Must only compare strong tags
-        out_headers.setHeader("ETag", http_headers.ETag('foo', weak=True))
-        request.headers.setRawHeaders("If-Match", ('W/"foo"',))
-        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
-
-    def testIfUnmodifiedSince(self):
-        request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
-        out_headers = http_headers.Headers()
-        response = http.Response(responsecode.OK, out_headers, None)
-
-        # No Last-Modified => always fail.
-        request.headers.setRawHeaders("If-Unmodified-Since", ('Mon, 03 Jan 2000 00:00:00 GMT',))
-        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
-
-        # Set output headers
-        out_headers.setHeader("ETag", http_headers.ETag('foo'))
-        out_headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
-
-        request.headers.setRawHeaders("If-Unmodified-Since", ('Mon, 03 Jan 2000 00:00:00 GMT',))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-        request.headers.setRawHeaders("If-Unmodified-Since", ('Sat, 01 Jan 2000 00:00:00 GMT',))
-        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
-
-        # But if we have an error code already, ignore this header
-        response.code = responsecode.INTERNAL_SERVER_ERROR
-        self.checkPreconditions(request, response, True, responsecode.INTERNAL_SERVER_ERROR)
-        response.code = responsecode.OK
-
-        # invalid date => header ignored
-        request.headers.setRawHeaders("If-Unmodified-Since", ('alalalalalalalalalala',))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-
-    def testIfModifiedSince(self):
-        if time.time() < 946771200:
-            self.fail(RuntimeError("Your computer's clock is way wrong, "
-                                   "this test will be invalid."))
-
-        request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
-        out_headers = http_headers.Headers()
-        response = http.Response(responsecode.OK, out_headers, None)
-
-        # No Last-Modified => always succeed
-        request.headers.setRawHeaders("If-Modified-Since", ('Mon, 03 Jan 2000 00:00:00 GMT',))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-        # Set output headers
-        out_headers.setHeader("ETag", http_headers.ETag('foo'))
-        out_headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
-
-        request.headers.setRawHeaders("If-Modified-Since", ('Mon, 03 Jan 2000 00:00:00 GMT',))
-        self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
-
-        # With a non-GET method
-        request.method="PUT"
-        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
-        request.method="GET"
-
-        request.headers.setRawHeaders("If-Modified-Since", ('Sat, 01 Jan 2000 00:00:00 GMT',))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-        # But if we have an error code already, ignore this header
-        response.code = responsecode.INTERNAL_SERVER_ERROR
-        self.checkPreconditions(request, response, True, responsecode.INTERNAL_SERVER_ERROR)
-        response.code = responsecode.OK
-
-        # invalid date => header ignored
-        request.headers.setRawHeaders("If-Modified-Since", ('alalalalalalalalalala',))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-        # date in the future => assume modified
-        request.headers.setHeader("If-Modified-Since", time.time() + 500)
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-    def testIfNoneMatch(self):
-        request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
-        out_headers = http_headers.Headers()
-        response = http.Response(responsecode.OK, out_headers, None)
-
-        request.headers.setRawHeaders("If-None-Match", ('"foo"',))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-        out_headers.setHeader("ETag", http_headers.ETag('foo'))
-        out_headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
-
-        # behavior of entityExists
-        request.headers.setRawHeaders("If-None-Match", ('*',))
-        request.method="PUT"
-        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
-        request.method="GET"
-        self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
-        self.checkPreconditions(request, response, True, responsecode.OK, entityExists=False)
-
-        # tag matches
-        request.headers.setRawHeaders("If-None-Match", ('"frob", "foo"',))
-        request.method="PUT"
-        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
-        request.method="GET"
-        self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
-
-        # now with IMS, also:
-        request.headers.setRawHeaders("If-Modified-Since", ('Mon, 03 Jan 2000 00:00:00 GMT',))
-        request.method="PUT"
-        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
-        request.method="GET"
-        self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
-
-        request.headers.setRawHeaders("If-Modified-Since", ('Sat, 01 Jan 2000 00:00:00 GMT',))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-        request.headers.removeHeader("If-Modified-Since")
-
-
-        # none match
-        request.headers.setRawHeaders("If-None-Match", ('"baz", "bob"',))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-        # now with IMS, also:
-        request.headers.setRawHeaders("If-Modified-Since", ('Mon, 03 Jan 2000 00:00:00 GMT',))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-        request.headers.setRawHeaders("If-Modified-Since", ('Sat, 01 Jan 2000 00:00:00 GMT',))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-        request.headers.removeHeader("If-Modified-Since")
-
-        # But if we have an error code already, ignore this header
-        response.code = responsecode.INTERNAL_SERVER_ERROR
-        self.checkPreconditions(request, response, True, responsecode.INTERNAL_SERVER_ERROR)
-        response.code = responsecode.OK
-
-        # Weak tags okay for GET
-        out_headers.setHeader("ETag", http_headers.ETag('foo', weak=True))
-        request.headers.setRawHeaders("If-None-Match", ('W/"foo"',))
-        self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
-
-        # Weak tags not okay for other methods
-        request.method="PUT"
-        out_headers.setHeader("ETag", http_headers.ETag('foo', weak=True))
-        request.headers.setRawHeaders("If-None-Match", ('W/"foo"',))
-        self.checkPreconditions(request, response, True, responsecode.OK)
-
-    def testNoResponse(self):
-        # Ensure that passing etag/lastModified arguments instead of response works.
-        request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
-        request.method="PUT"
-        request.headers.setRawHeaders("If-None-Match", ('"foo"',))
-
-        self.checkPreconditions(request, None, True, responsecode.OK)
-        self.checkPreconditions(request, None, False, responsecode.PRECONDITION_FAILED,
-                                etag=http_headers.ETag('foo'),
-                                lastModified=946771200)
-
-        # Make sure that, while you shoudn't do this, that it doesn't cause an error
-        request.method="GET"
-        self.checkPreconditions(request, None, False, responsecode.NOT_MODIFIED,
-                                etag=http_headers.ETag('foo'))
-
-class IfRangeTestCase(unittest.TestCase):
-    def testIfRange(self):
-        request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
-        response = TestResponse()
-
-        self.assertEquals(http.checkIfRange(request, response), True)
-
-        request.headers.setRawHeaders("If-Range", ('"foo"',))
-        self.assertEquals(http.checkIfRange(request, response), False)
-
-        response.headers.setHeader("ETag", http_headers.ETag('foo'))
-        self.assertEquals(http.checkIfRange(request, response), True)
-
-        request.headers.setRawHeaders("If-Range", ('"bar"',))
-        response.headers.setHeader("ETag", http_headers.ETag('foo'))
-        self.assertEquals(http.checkIfRange(request, response), False)
-
-        request.headers.setRawHeaders("If-Range", ('W/"foo"',))
-        response.headers.setHeader("ETag", http_headers.ETag('foo', weak=True))
-        self.assertEquals(http.checkIfRange(request, response), False)
-
-        request.headers.setRawHeaders("If-Range", ('"foo"',))
-        response.headers.removeHeader("ETag")
-        self.assertEquals(http.checkIfRange(request, response), False)
-
-        request.headers.setRawHeaders("If-Range", ('Sun, 02 Jan 2000 00:00:00 GMT',))
-        response.headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
-        self.assertEquals(http.checkIfRange(request, response), True)
-
-        request.headers.setRawHeaders("If-Range", ('Sun, 02 Jan 2000 00:00:01 GMT',))
-        response.headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
-        self.assertEquals(http.checkIfRange(request, response), False)
-
-        request.headers.setRawHeaders("If-Range", ('Sun, 01 Jan 2000 23:59:59 GMT',))
-        response.headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
-        self.assertEquals(http.checkIfRange(request, response), False)
-
-        request.headers.setRawHeaders("If-Range", ('Sun, 01 Jan 2000 23:59:59 GMT',))
-        response.headers.removeHeader("Last-Modified")
-        self.assertEquals(http.checkIfRange(request, response), False)
-
-        request.headers.setRawHeaders("If-Range", ('jwerlqjL#$Y*KJAN',))
-        self.assertEquals(http.checkIfRange(request, response), False)
-
-
-
-class LoopbackRelay(loopback.LoopbackRelay):
-    implements(interfaces.IProducer)
-
-    def pauseProducing(self):
-        self.paused = True
-
-    def resumeProducing(self):
-        self.paused = False
-
-    def stopProducing(self):
-        self.loseConnection()
-
-    def loseWriteConnection(self):
-        # HACK.
-        self.loseConnection()
-
-class TestRequest(http.Request):
-    def __init__(self, *args, **kwargs):
-        http.Request.__init__(self, *args, **kwargs)
-        self.cmds = []
-        headers = list(self.headers.getAllRawHeaders())
-        headers.sort()
-        self.cmds.append(('init', self.method, self.uri, self.clientproto, self.stream.length, tuple(headers)))
-
-    def process(self):
-        pass
-    def handleContentChunk(self, data):
-        self.cmds.append(('contentChunk', data))
-
-    def handleContentComplete(self):
-        self.cmds.append(('contentComplete',))
-
-    def connectionLost(self, reason):
-        self.cmds.append(('connectionLost', reason))
-
-    def _finished(self, x):
-        self._reallyFinished(x)
-
-class TestResponse(object):
-    implements(iweb.IResponse)
-
-    code = responsecode.OK
-    headers = None
-
-    def __init__(self):
-        self.headers = http_headers.Headers()
-        self.stream = stream.ProducerStream()
-
-    def write(self, data):
-        self.stream.write(data)
-
-    def finish(self):
-        self.stream.finish()
-
-class TestClient(protocol.Protocol):
-    data = ""
-    done = False
-
-    def dataReceived(self, data):
-        self.data+=data
-
-    def write(self, data):
-        self.transport.write(data)
-
-    def connectionLost(self, reason):
-        self.done = True
-        self.transport.loseConnection()
-
-    def loseConnection(self):
-        self.done = True
-        self.transport.loseConnection()
-
-class TestConnection:
-    def __init__(self):
-        self.requests = []
-        self.client = None
-        self.callLaters = []
-
-    def fakeCallLater(self, secs, f):
-        assert secs == 0
-        self.callLaters.append(f)
-
-class HTTPTests(unittest.TestCase):
-    def connect(self, logFile=None, **protocol_kwargs):
-        cxn = TestConnection()
-
-        def makeTestRequest(*args):
-            cxn.requests.append(TestRequest(*args))
-            return cxn.requests[-1]
-
-        factory = channel.HTTPFactory(requestFactory=makeTestRequest,
-                                      _callLater=cxn.fakeCallLater,
-                                      **protocol_kwargs)
-
-        cxn.client = TestClient()
-        cxn.server = factory.buildProtocol(address.IPv4Address('TCP', '127.0.0.1', 2345))
-
-        cxn.serverToClient = LoopbackRelay(cxn.client, logFile)
-        cxn.clientToServer = LoopbackRelay(cxn.server, logFile)
-        cxn.server.makeConnection(cxn.serverToClient)
-        cxn.client.makeConnection(cxn.clientToServer)
-
-        return cxn
-
-    def iterate(self, cxn):
-        callLaters = cxn.callLaters
-        cxn.callLaters = []
-        for f in callLaters:
-            f()
-        cxn.serverToClient.clearBuffer()
-        cxn.clientToServer.clearBuffer()
-        if cxn.serverToClient.shouldLose:
-            cxn.serverToClient.clearBuffer()
-        if cxn.clientToServer.shouldLose:
-            cxn.clientToServer.clearBuffer()
-
-    def compareResult(self, cxn, cmds, data):
-        self.iterate(cxn)
-        for receivedRequest, expectedCommands in map(None, cxn.requests, cmds):
-            sortedHeaderCommands = []
-            for cmd in expectedCommands:
-                if len(cmd) == 6:
-                    sortedHeaders = list(cmd[5])
-                    sortedHeaders.sort()
-                    sortedHeaderCommands.append(cmd[:5] + (tuple(sortedHeaders),))
-                else:
-                    sortedHeaderCommands.append(cmd)
-            self.assertEquals(receivedRequest.cmds, sortedHeaderCommands)
-        self.assertEquals(cxn.client.data, data)
-
-    def assertDone(self, cxn, done=True):
-        self.iterate(cxn)
-        self.assertEquals(cxn.client.done, done)
-
-
-class CoreHTTPTestCase(HTTPTests):
-    # Note: these tests compare the client output using string
-    #       matching. It is acceptable for this to change and break
-    #       the test if you know what you are doing.
-
-    def testHTTP0_9(self, nouri=False):
-        cxn = self.connect()
-        cmds = [[]]
-        data = ""
-
-        if nouri:
-            cxn.client.write("GET\r\n")
-        else:
-            cxn.client.write("GET /\r\n")
-        # Second request which should not be handled
-        cxn.client.write("GET /two\r\n")
-
-        cmds[0] += [('init', 'GET', '/', (0,9), 0, ()), ('contentComplete',)]
-        self.compareResult(cxn, cmds, data)
-
-        response = TestResponse()
-        response.headers.setRawHeaders("Yo", ("One", "Two"))
-        cxn.requests[0].writeResponse(response)
-        response.write("")
-
-        self.compareResult(cxn, cmds, data)
-
-        response.write("Output")
-        data += "Output"
-        self.compareResult(cxn, cmds, data)
-
-        response.finish()
-        self.compareResult(cxn, cmds, data)
-
-        self.assertDone(cxn)
-
-    def testHTTP0_9_nouri(self):
-        self.testHTTP0_9(True)
-
-    def testHTTP1_0(self):
-        cxn = self.connect()
-        cmds = [[]]
-        data = ""
-
-        cxn.client.write("GET / HTTP/1.0\r\nContent-Length: 5\r\nHost: localhost\r\n\r\nInput")
-        # Second request which should not be handled
-        cxn.client.write("GET /two HTTP/1.0\r\n\r\n")
-
-        cmds[0] += [('init', 'GET', '/', (1,0), 5,
-                     (('Host', ['localhost']),)),
-                    ('contentChunk', 'Input'),
-                    ('contentComplete',)]
-        self.compareResult(cxn, cmds, data)
-
-        response = TestResponse()
-        response.headers.setRawHeaders("Yo", ("One", "Two"))
-        cxn.requests[0].writeResponse(response)
-        response.write("")
-
-        data += "HTTP/1.1 200 OK\r\nYo: One\r\nYo: Two\r\nConnection: close\r\n\r\n"
-        self.compareResult(cxn, cmds, data)
-
-        response.write("Output")
-        data += "Output"
-        self.compareResult(cxn, cmds, data)
-
-        response.finish()
-        self.compareResult(cxn, cmds, data)
-
-        self.assertDone(cxn)
-
-    def testHTTP1_0_keepalive(self):
-        cxn = self.connect()
-        cmds = [[]]
-        data = ""
-
-        cxn.client.write("GET / HTTP/1.0\r\nConnection: keep-alive\r\nContent-Length: 5\r\nHost: localhost\r\n\r\nInput")
-        cxn.client.write("GET /two HTTP/1.0\r\n\r\n")
-        # Third request shouldn't be handled
-        cxn.client.write("GET /three HTTP/1.0\r\n\r\n")
-
-        cmds[0] += [('init', 'GET', '/', (1,0), 5,
-                     (('Host', ['localhost']),)),
-                    ('contentChunk', 'Input'),
-                    ('contentComplete',)]
-        self.compareResult(cxn, cmds, data)
-
-        response0 = TestResponse()
-        response0.headers.setRawHeaders("Content-Length", ("6", ))
-        response0.headers.setRawHeaders("Yo", ("One", "Two"))
-        cxn.requests[0].writeResponse(response0)
-        response0.write("")
-
-        data += "HTTP/1.1 200 OK\r\nContent-Length: 6\r\nYo: One\r\nYo: Two\r\nConnection: Keep-Alive\r\n\r\n"
-        self.compareResult(cxn, cmds, data)
-
-        response0.write("Output")
-        data += "Output"
-        self.compareResult(cxn, cmds, data)
-
-        response0.finish()
-
-        # Now for second request:
-        cmds.append([])
-        cmds[1] += [('init', 'GET', '/two', (1,0), 0, ()), 
-                    ('contentComplete',)]
-        self.compareResult(cxn, cmds, data)
-
-
-        response1 = TestResponse()
-        response1.headers.setRawHeaders("Content-Length", ("0", ))
-        cxn.requests[1].writeResponse(response1)
-        response1.write("")
-
-        data += "HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
-        self.compareResult(cxn, cmds, data)
-        response1.finish()
-
-        self.assertDone(cxn)
-
-    def testHTTP1_1_pipelining(self):
-        cxn = self.connect(maxPipeline=2)
-        cmds = []
-        data = ""
-
-        # Both these show up immediately.
-        cxn.client.write("GET / HTTP/1.1\r\nContent-Length: 5\r\nHost: localhost\r\n\r\nInput")
-        cxn.client.write("GET /two HTTP/1.1\r\nHost: localhost\r\n\r\n")
-        # Doesn't show up until the first is done.
-        cxn.client.write("GET /three HTTP/1.1\r\nHost: localhost\r\n\r\n")
-        # Doesn't show up until the second is done.
-        cxn.client.write("GET /four HTTP/1.1\r\nHost: localhost\r\n\r\n")
-
-        cmds.append([])
-        cmds[0] += [('init', 'GET', '/', (1,1), 5, 
-                     (('Host', ['localhost']),)),
-                    ('contentChunk', 'Input'),
-                    ('contentComplete',)]
-        cmds.append([])
-        cmds[1] += [('init', 'GET', '/two', (1,1), 0, 
-                     (('Host', ['localhost']),)),
-                    ('contentComplete',)]
-
-        self.compareResult(cxn, cmds, data)
-
-        response0 = TestResponse()
-        response0.headers.setRawHeaders("Content-Length", ("6", ))
-        cxn.requests[0].writeResponse(response0)
-        response0.write("")
-
-        data += "HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\n"
-        self.compareResult(cxn, cmds, data)
-
-        response0.write("Output")
-        data += "Output"
-        self.compareResult(cxn, cmds, data)
-
-        response0.finish()
-
-        # Now the third request gets read:
-        cmds.append([])
-        cmds[2] += [('init', 'GET', '/three', (1,1), 0,
-                     (('Host', ['localhost']),)),
-                    ('contentComplete',)]
-        self.compareResult(cxn, cmds, data)
-
-        # Let's write out the third request before the second.
-        # This should not cause anything to be written to the client.
-        response2 = TestResponse()
-        response2.headers.setRawHeaders("Content-Length", ("5", ))
-        cxn.requests[2].writeResponse(response2)
-
-        response2.write("Three")
-        response2.finish()
-
-        self.compareResult(cxn, cmds, data)
-
-        response1 = TestResponse()
-        response1.headers.setRawHeaders("Content-Length", ("3", ))
-        cxn.requests[1].writeResponse(response1)
-        response1.write("Two")
-
-        data += "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nTwo"
-        self.compareResult(cxn, cmds, data)
-
-        response1.finish()
-
-        # Fourth request shows up
-        cmds.append([])
-        cmds[3] += [('init', 'GET', '/four', (1,1), 0,
-                     (('Host', ['localhost']),)),
-                    ('contentComplete',)]
-        data += "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nThree"
-        self.compareResult(cxn, cmds, data)
-
-        response3 = TestResponse()
-        response3.headers.setRawHeaders("Content-Length", ("0",))
-        cxn.requests[3].writeResponse(response3)
-        response3.finish()
-
-        data += "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
-        self.compareResult(cxn, cmds, data)
-
-        self.assertDone(cxn, done=False)
-        cxn.client.loseConnection()
-        self.assertDone(cxn)
-
-    def testHTTP1_1_chunking(self):
-        cxn = self.connect()
-        cmds = [[]]
-        data = ""
-        cxn.client.write("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\nHost: localhost\r\n\r\n5\r\nInput\r\n")
-
-        cmds[0] += [('init', 'GET', '/', (1,1), None,
-                     (('Host', ['localhost']),)),
-                    ('contentChunk', 'Input')]
-
-        self.compareResult(cxn, cmds, data)
-
-        cxn.client.write("1; blahblahblah\r\na\r\n10\r\nabcdefghijklmnop\r\n")
-        cmds[0] += [('contentChunk', 'a'),('contentChunk', 'abcdefghijklmnop')]
-        self.compareResult(cxn, cmds, data)
-
-        cxn.client.write("0\r\nRandom-Ignored-Trailer: foo\r\n\r\n")
-        cmds[0] += [('contentComplete',)]
-        self.compareResult(cxn, cmds, data)
-
-        response = TestResponse()
-        cxn.requests[0].writeResponse(response)
-        response.write("Output")
-        data += "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nOutput\r\n"
-        self.compareResult(cxn, cmds, data)
-
-        response.write("blahblahblah")
-        data += "C\r\nblahblahblah\r\n"
-        self.compareResult(cxn, cmds, data)
-
-        response.finish()
-        data += "0\r\n\r\n"
-        self.compareResult(cxn, cmds, data)
-
-        cxn.client.loseConnection()
-        self.assertDone(cxn)
-
-    def testHTTP1_1_expect_continue(self):
-        cxn = self.connect()
-        cmds = [[]]
-        data = ""
-        cxn.client.write("GET / HTTP/1.1\r\nContent-Length: 5\r\nHost: localhost\r\nExpect: 100-continue\r\n\r\n")
-        cmds[0] += [('init', 'GET', '/', (1,1), 5,
-                     (('Expect', ['100-continue']), ('Host', ['localhost'])))]
-        self.compareResult(cxn, cmds, data)
-
-        cxn.requests[0].stream.read()
-        data += "HTTP/1.1 100 Continue\r\n\r\n"
-        self.compareResult(cxn, cmds, data)
-
-        cxn.client.write("Input")
-        cmds[0] += [('contentChunk', 'Input'),
-                    ('contentComplete',)]
-        self.compareResult(cxn, cmds, data)
-
-        response = TestResponse()
-        response.headers.setRawHeaders("Content-Length", ("6",))
-        cxn.requests[0].writeResponse(response)
-        response.write("Output")
-        response.finish()
-
-        data += "HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nOutput"
-        self.compareResult(cxn, cmds, data)
-
-        cxn.client.loseConnection()
-        self.assertDone(cxn)
-
-    def testHTTP1_1_expect_continue_early_reply(self):
-        cxn = self.connect()
-        cmds = [[]]
-        data = ""
-        cxn.client.write("GET / HTTP/1.1\r\nContent-Length: 5\r\nHost: localhost\r\nExpect: 100-continue\r\n\r\n")
-        cmds[0] += [('init', 'GET', '/', (1,1), 5,
-                     (('Host', ['localhost']), ('Expect', ['100-continue'])))]
-        self.compareResult(cxn, cmds, data)
-
-        response = TestResponse()
-        response.headers.setRawHeaders("Content-Length", ("6",))
-        cxn.requests[0].writeResponse(response)
-        response.write("Output")
-        response.finish()
-
-        cmds[0] += [('contentComplete',)]
-        data += "HTTP/1.1 200 OK\r\nContent-Length: 6\r\nConnection: close\r\n\r\nOutput"
-        self.compareResult(cxn, cmds, data)
-
-        cxn.client.loseConnection()
-        self.assertDone(cxn)
-
-    def testHeaderContinuation(self):
-        cxn = self.connect()
-        cmds = [[]]
-        data = ""
-
-        cxn.client.write("GET / HTTP/1.1\r\nHost: localhost\r\nFoo: yada\r\n yada\r\n\r\n")
-        cmds[0] += [('init', 'GET', '/', (1,1), 0,
-                     (('Host', ['localhost']), ('Foo', ['yada yada']),)),
-                    ('contentComplete',)]
-        self.compareResult(cxn, cmds, data)
-
-        cxn.client.loseConnection()
-        self.assertDone(cxn)
-
-    def testTimeout_immediate(self):
-        # timeout 0 => timeout on first iterate call
-        cxn = self.connect(inputTimeOut = 0)
-        return deferLater(reactor, 0, self.assertDone, cxn)
-
-    def testTimeout_inRequest(self):
-        cxn = self.connect(inputTimeOut = 0.3)
-        cmds = [[]]
-        data = ""
-
-        cxn.client.write("GET / HTTP/1.1\r\n")
-        return deferLater(reactor, 0.5, self.assertDone, cxn)
-
-    def testTimeout_betweenRequests(self):
-        cxn = self.connect(betweenRequestsTimeOut = 0.3)
-        cmds = [[]]
-        data = ""
-
-        cxn.client.write("GET / HTTP/1.1\r\n\r\n")
-        cmds[0] += [('init', 'GET', '/', (1,1), 0, ()),
-                    ('contentComplete',)]
-        self.compareResult(cxn, cmds, data)
-
-        response = TestResponse()
-        response.headers.setRawHeaders("Content-Length", ("0",))
-        cxn.requests[0].writeResponse(response)
-        response.finish()
-
-        data += "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
-
-        self.compareResult(cxn, cmds, data)
-        return deferLater(reactor, 0.5, self.assertDone, cxn) # Wait for timeout
-
-    def testConnectionCloseRequested(self):
-        cxn = self.connect()
-        cmds = [[]]
-        data = ""
-
-        cxn.client.write("GET / HTTP/1.1\r\n\r\n")
-        cmds[0] += [('init', 'GET', '/', (1,1), 0, ()),
-                    ('contentComplete',)]
-        self.compareResult(cxn, cmds, data)
-
-        cxn.client.write("GET / HTTP/1.1\r\nConnection: close\r\n\r\n")
-        cmds.append([])
-        cmds[1] += [('init', 'GET', '/', (1,1), 0, ()),
-                    ('contentComplete',)]
-        self.compareResult(cxn, cmds, data)
-
-        response = TestResponse()
-        response.headers.setRawHeaders("Content-Length", ("0",))
-        cxn.requests[0].writeResponse(response)
-        response.finish()
-
-        data += "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
-
-        response = TestResponse()
-        response.headers.setRawHeaders("Content-Length", ("0",))
-        cxn.requests[1].writeResponse(response)
-        response.finish()
-
-        data += "HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
-
-        self.compareResult(cxn, cmds, data)
-        self.assertDone(cxn)
-
-    def testExtraCRLFs(self):
-        cxn = self.connect()
-        cmds = [[]]
-        data = ""
-
-        # Some broken clients (old IEs) send an extra CRLF after post
-        cxn.client.write("POST / HTTP/1.1\r\nContent-Length: 5\r\nHost: localhost\r\n\r\nInput\r\n")
-        cmds[0] += [('init', 'POST', '/', (1,1), 5,
-                     (('Host', ['localhost']),)),
-                    ('contentChunk', 'Input'),
-                    ('contentComplete',)]
-
-        self.compareResult(cxn, cmds, data)
-
-        cxn.client.write("GET /two HTTP/1.1\r\n\r\n")
-        cmds.append([])
-        cmds[1] += [('init', 'GET', '/two', (1,1), 0, ()),
-                    ('contentComplete',)]
-        self.compareResult(cxn, cmds, data)
-
-        cxn.client.loseConnection()
-        self.assertDone(cxn)
-
-    def testDisallowPersistentConnections(self):
-        cxn = self.connect(allowPersistentConnections=False)
-        cmds = [[]]
-        data = ""
-
-        cxn.client.write("GET / HTTP/1.1\r\nHost: localhost\r\n\r\nGET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
-        cmds[0] += [('init', 'GET', '/', (1,1), 0,
-                     (('Host', ['localhost']),)),
-                    ('contentComplete',)]
-        self.compareResult(cxn, cmds, data)
-
-        response = TestResponse()
-        response.finish()
-        cxn.requests[0].writeResponse(response)
-        data += 'HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n'
-        self.compareResult(cxn, cmds, data)
-        self.assertDone(cxn)
-
-    def testIgnoreBogusContentLength(self):
-        # Ensure that content-length is ignored when transfer-encoding
-        # is also specified.
-        cxn = self.connect()
-        cmds = [[]]
-        data = ""
-        cxn.client.write("GET / HTTP/1.1\r\nContent-Length: 100\r\nTransfer-Encoding: chunked\r\nHost: localhost\r\n\r\n5\r\nInput\r\n")
-
-        cmds[0] += [('init', 'GET', '/', (1,1), None,
-                     (('Host', ['localhost']),)),
-                    ('contentChunk', 'Input')]
-
-        self.compareResult(cxn, cmds, data)
-
-        cxn.client.write("0\r\n\r\n")
-        cmds[0] += [('contentComplete',)]
-        self.compareResult(cxn, cmds, data)
-
-        response = TestResponse()
-        response.finish()
-        cxn.requests[0].writeResponse(response)
-        data += "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
-        self.compareResult(cxn, cmds, data)
-
-        cxn.client.loseConnection()
-        self.assertDone(cxn)
-
-class ErrorTestCase(HTTPTests):
-    def assertStartsWith(self, first, second, msg=None):
-        self.assert_(first.startswith(second), '%r.startswith(%r)' % (first, second))
-
-    def checkError(self, cxn, code):
-        self.iterate(cxn)
-        self.assertStartsWith(cxn.client.data, "HTTP/1.1 %d "%code)
-        self.assertIn("\r\nConnection: close\r\n", cxn.client.data)
-        # Ensure error messages have a defined content-length.
-        self.assertIn("\r\nContent-Length:", cxn.client.data)
-        self.assertDone(cxn)
-
-    def testChunkingError1(self):
-        cxn = self.connect()
-        cxn.client.write("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\nasdf\r\n")
-
-        self.checkError(cxn, 400)
-
-    def testChunkingError2(self):
-        cxn = self.connect()
-        cxn.client.write("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nblahblah\r\n")
-
-        self.checkError(cxn, 400)
-
-    def testChunkingError3(self):
-        cxn = self.connect()
-        cxn.client.write("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n-1\r\nasdf\r\n")
-
-        self.checkError(cxn, 400)
-
-    def testTooManyHeaders(self):
-        cxn = self.connect()
-        cxn.client.write("GET / HTTP/1.1\r\n")
-        cxn.client.write("Foo: Bar\r\n"*5000)
-
-        self.checkError(cxn, 400)
-
-    def testLineTooLong(self):
-        cxn = self.connect()
-        cxn.client.write("GET / HTTP/1.1\r\n")
-        cxn.client.write("Foo: "+("Bar"*10000))
-
-        self.checkError(cxn, 400)
-
-    def testLineTooLong2(self):
-        cxn = self.connect()
-        cxn.client.write("GET "+("/Bar")*10000 +" HTTP/1.1\r\n")
-
-        self.checkError(cxn, 414)
-
-    def testNoColon(self):
-        cxn = self.connect()
-        cxn.client.write("GET / HTTP/1.1\r\n")
-        cxn.client.write("Blahblah\r\n\r\n")
-
-        self.checkError(cxn, 400)
-
-    def testBadRequest(self):
-        cxn = self.connect()
-        cxn.client.write("GET / more HTTP/1.1\r\n")
-
-        self.checkError(cxn, 400)
-
-    def testWrongProtocol(self):
-        cxn = self.connect()
-        cxn.client.write("GET / Foobar/1.0\r\n")
-
-        self.checkError(cxn, 400)
-
-    def testBadProtocolVersion(self):
-        cxn = self.connect()
-        cxn.client.write("GET / HTTP/1\r\n")
-
-        self.checkError(cxn, 400)
-
-    def testBadProtocolVersion2(self):
-        cxn = self.connect()
-        cxn.client.write("GET / HTTP/-1.0\r\n")
-
-        self.checkError(cxn, 400)
-
-    def testWrongProtocolVersion(self):
-        cxn = self.connect()
-        cxn.client.write("GET / HTTP/2.0\r\n")
-
-        self.checkError(cxn, 505)
-
-    def testUnsupportedTE(self):
-        cxn = self.connect()
-        cxn.client.write("GET / HTTP/1.1\r\n")
-        cxn.client.write("Transfer-Encoding: blahblahblah, chunked\r\n\r\n")
-        self.checkError(cxn, 501)
-
-    def testTEWithoutChunked(self):
-        cxn = self.connect()
-        cxn.client.write("GET / HTTP/1.1\r\n")
-        cxn.client.write("Transfer-Encoding: gzip\r\n\r\n")
-        self.checkError(cxn, 400)
-
-class PipelinedErrorTestCase(ErrorTestCase):
-    # Make sure that even low level reading errors don't corrupt the data stream,
-    # but always wait until their turn to respond.
-
-    def connect(self):
-        cxn = ErrorTestCase.connect(self)
-        cxn.client.write("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
-
-        cmds = [[('init', 'GET', '/', (1,1), 0,
-                 (('Host', ['localhost']),)),
-                ('contentComplete', )]]
-        data = ""
-        self.compareResult(cxn, cmds, data)
-        return cxn
-
-    def checkError(self, cxn, code):
-        self.iterate(cxn)
-        self.assertEquals(cxn.client.data, '')
-
-        response = TestResponse()
-        response.headers.setRawHeaders("Content-Length", ("0",))
-        cxn.requests[0].writeResponse(response)
-        response.write('')
-
-        data = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
-        self.iterate(cxn)
-        self.assertEquals(cxn.client.data, data)
-
-        # Reset the data so the checkError's startswith test can work right.
-        cxn.client.data = ""
-
-        response.finish()
-        ErrorTestCase.checkError(self, cxn, code)
-
-
-class SimpleFactory(channel.HTTPFactory):
-    def buildProtocol(self, addr):
-        # Do a bunch of crazy crap just so that the test case can know when the
-        # connection is done.
-        p = channel.HTTPFactory.buildProtocol(self, addr)
-        cl = p.connectionLost
-        def newCl(reason):
-            reactor.callLater(0, lambda: self.testcase.connlost.callback(None))
-            return cl(reason)
-        p.connectionLost = newCl
-        self.conn = p
-        return p
-
-class SimpleRequest(http.Request):
-    def process(self):
-        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)
-        response.finish()
-        self.writeResponse(response)
-
-class AbstractServerTestMixin:
-    type = None
-    def testBasicWorkingness(self):
-        args = ('-u', util.sibpath(__file__, "simple_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 402 Payment Required\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")
-    testBasicWorkingness = deferredGenerator(testBasicWorkingness)
-
-    def testLingeringClose(self):
-        args = ('-u', util.sibpath(__file__, "simple_client.py"),
-                "lingeringClose", 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 402 Payment Required\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")
-    testLingeringClose = deferredGenerator(testLingeringClose)
-
-class TCPServerTest(unittest.TestCase, AbstractServerTestMixin):
-    type = 'tcp'
-    def setUp(self):
-        factory=SimpleFactory(requestFactory=SimpleRequest)
-
-        factory.testcase = self
-        self.factory = factory
-        self.connlost = defer.Deferred()
-
-        self.socket = reactor.listenTCP(0, factory)
-        self.port = self.socket.getHost().port
-
-    def tearDown(self):
-        # Make sure the listening port is closed
-        d = defer.maybeDeferred(self.socket.stopListening)
-
-        def finish(v):
-            # And make sure the established connection is, too
-            self.factory.conn.transport.loseConnection()
-            return self.connlost
-        return d.addCallback(finish)
-
-
-try:
-    from twisted.internet import ssl
-except ImportError:
-   # happens the first time the interpreter tries to import it
-   ssl = None
-if ssl and not ssl.supported:
-   # happens second and later times
-   ssl = None
-
-certPath = util.sibpath(__file__, "server.pem")
-
-class SSLServerTest(unittest.TestCase, AbstractServerTestMixin):
-    type = 'ssl'
-    def setUp(self):
-        sCTX = ssl.DefaultOpenSSLContextFactory(certPath, certPath)
-        factory=SimpleFactory(requestFactory=SimpleRequest)
-
-        factory.testcase = self
-        self.factory = factory
-        self.connlost = defer.Deferred()
-
-        self.socket = reactor.listenSSL(0, factory, sCTX)
-        self.port = self.socket.getHost().port
-
-    def tearDown(self):
-        # Make sure the listening port is closed
-        d = defer.maybeDeferred(self.socket.stopListening)
-
-        def finish(v):
-            # And make sure the established connection is, too
-            self.factory.conn.transport.loseConnection()
-            return self.connlost
-        return d.addCallback(finish)
-
-    def testLingeringClose(self):
-        return super(SSLServerTest, self).testLingeringClose()
-
-    if runtime.platform.isWindows():
-        # This may not just be Windows, but all platforms with more recent
-        # versions of OpenSSL.  Do some more experimentation...
-        testLingeringClose.todo = "buffering kills the connection too early; test this some other way"
-
-
-if interfaces.IReactorProcess(reactor, None) is None:
-    TCPServerTest.skip = SSLServerTest.skip = "Required process support missing from reactor"
-elif interfaces.IReactorSSL(reactor, None) is None:
-    SSLServerTest.skip = "Required SSL support missing from reactor"
-elif ssl is None:
-    SSLServerTest.skip = "SSL not available, cannot test SSL."

Copied: CalendarServer/trunk/twext/web2/test/test_http.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_http.py)
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_http.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/test/test_http.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,1120 @@
+
+from __future__ import nested_scopes
+
+import time, sys
+
+from zope.interface import implements
+
+from twisted.trial import unittest
+from twext.web2 import http, http_headers, responsecode, error, iweb, stream
+from twext.web2 import channel
+
+from twisted.internet import reactor, protocol, address, interfaces, utils
+from twisted.internet import defer
+from twisted.internet.defer import waitForDeferred, deferredGenerator
+from twisted.protocols import loopback
+from twisted.python import util, runtime
+from twisted.internet.task import deferLater
+
+class PreconditionTestCase(unittest.TestCase):
+    def checkPreconditions(self, request, response, expectedResult, expectedCode,
+                           **kw):
+        preconditionsPass = True
+
+        try:
+            http.checkPreconditions(request, response, **kw)
+        except http.HTTPError, e:
+            preconditionsPass = False
+            self.assertEquals(e.response.code, expectedCode)
+        self.assertEquals(preconditionsPass, expectedResult)
+
+    def testWithoutHeaders(self):
+        request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
+        out_headers = http_headers.Headers()
+        response = http.Response(responsecode.OK, out_headers, None)
+
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+        out_headers.setHeader("ETag", http_headers.ETag('foo'))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+        out_headers.removeHeader("ETag")
+        out_headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+        out_headers.setHeader("ETag", http_headers.ETag('foo'))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+    def testIfMatch(self):
+        request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
+        out_headers = http_headers.Headers()
+        response = http.Response(responsecode.OK, out_headers, None)
+
+        # Behavior with no ETag set, should be same as with an ETag
+        request.headers.setRawHeaders("If-Match", ('*',))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED, entityExists=False)
+
+        # Ask for tag, but no etag set.
+        request.headers.setRawHeaders("If-Match", ('"frob"',))
+        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
+
+        ## Actually set the ETag header
+        out_headers.setHeader("ETag", http_headers.ETag('foo'))
+        out_headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
+
+        # behavior of entityExists
+        request.headers.setRawHeaders("If-Match", ('*',))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED, entityExists=False)
+
+        # tag matches
+        request.headers.setRawHeaders("If-Match", ('"frob", "foo"',))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+        # none match
+        request.headers.setRawHeaders("If-Match", ('"baz", "bob"',))
+        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
+
+        # But if we have an error code already, ignore this header
+        response.code = responsecode.INTERNAL_SERVER_ERROR
+        self.checkPreconditions(request, response, True, responsecode.INTERNAL_SERVER_ERROR)
+        response.code = responsecode.OK
+
+        # Must only compare strong tags
+        out_headers.setHeader("ETag", http_headers.ETag('foo', weak=True))
+        request.headers.setRawHeaders("If-Match", ('W/"foo"',))
+        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
+
+    def testIfUnmodifiedSince(self):
+        request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
+        out_headers = http_headers.Headers()
+        response = http.Response(responsecode.OK, out_headers, None)
+
+        # No Last-Modified => always fail.
+        request.headers.setRawHeaders("If-Unmodified-Since", ('Mon, 03 Jan 2000 00:00:00 GMT',))
+        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
+
+        # Set output headers
+        out_headers.setHeader("ETag", http_headers.ETag('foo'))
+        out_headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
+
+        request.headers.setRawHeaders("If-Unmodified-Since", ('Mon, 03 Jan 2000 00:00:00 GMT',))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+        request.headers.setRawHeaders("If-Unmodified-Since", ('Sat, 01 Jan 2000 00:00:00 GMT',))
+        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
+
+        # But if we have an error code already, ignore this header
+        response.code = responsecode.INTERNAL_SERVER_ERROR
+        self.checkPreconditions(request, response, True, responsecode.INTERNAL_SERVER_ERROR)
+        response.code = responsecode.OK
+
+        # invalid date => header ignored
+        request.headers.setRawHeaders("If-Unmodified-Since", ('alalalalalalalalalala',))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+
+    def testIfModifiedSince(self):
+        if time.time() < 946771200:
+            self.fail(RuntimeError("Your computer's clock is way wrong, "
+                                   "this test will be invalid."))
+
+        request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
+        out_headers = http_headers.Headers()
+        response = http.Response(responsecode.OK, out_headers, None)
+
+        # No Last-Modified => always succeed
+        request.headers.setRawHeaders("If-Modified-Since", ('Mon, 03 Jan 2000 00:00:00 GMT',))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+        # Set output headers
+        out_headers.setHeader("ETag", http_headers.ETag('foo'))
+        out_headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
+
+        request.headers.setRawHeaders("If-Modified-Since", ('Mon, 03 Jan 2000 00:00:00 GMT',))
+        self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
+
+        # With a non-GET method
+        request.method="PUT"
+        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
+        request.method="GET"
+
+        request.headers.setRawHeaders("If-Modified-Since", ('Sat, 01 Jan 2000 00:00:00 GMT',))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+        # But if we have an error code already, ignore this header
+        response.code = responsecode.INTERNAL_SERVER_ERROR
+        self.checkPreconditions(request, response, True, responsecode.INTERNAL_SERVER_ERROR)
+        response.code = responsecode.OK
+
+        # invalid date => header ignored
+        request.headers.setRawHeaders("If-Modified-Since", ('alalalalalalalalalala',))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+        # date in the future => assume modified
+        request.headers.setHeader("If-Modified-Since", time.time() + 500)
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+    def testIfNoneMatch(self):
+        request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
+        out_headers = http_headers.Headers()
+        response = http.Response(responsecode.OK, out_headers, None)
+
+        request.headers.setRawHeaders("If-None-Match", ('"foo"',))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+        out_headers.setHeader("ETag", http_headers.ETag('foo'))
+        out_headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
+
+        # behavior of entityExists
+        request.headers.setRawHeaders("If-None-Match", ('*',))
+        request.method="PUT"
+        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
+        request.method="GET"
+        self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
+        self.checkPreconditions(request, response, True, responsecode.OK, entityExists=False)
+
+        # tag matches
+        request.headers.setRawHeaders("If-None-Match", ('"frob", "foo"',))
+        request.method="PUT"
+        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
+        request.method="GET"
+        self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
+
+        # now with IMS, also:
+        request.headers.setRawHeaders("If-Modified-Since", ('Mon, 03 Jan 2000 00:00:00 GMT',))
+        request.method="PUT"
+        self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
+        request.method="GET"
+        self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
+
+        request.headers.setRawHeaders("If-Modified-Since", ('Sat, 01 Jan 2000 00:00:00 GMT',))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+        request.headers.removeHeader("If-Modified-Since")
+
+
+        # none match
+        request.headers.setRawHeaders("If-None-Match", ('"baz", "bob"',))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+        # now with IMS, also:
+        request.headers.setRawHeaders("If-Modified-Since", ('Mon, 03 Jan 2000 00:00:00 GMT',))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+        request.headers.setRawHeaders("If-Modified-Since", ('Sat, 01 Jan 2000 00:00:00 GMT',))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+        request.headers.removeHeader("If-Modified-Since")
+
+        # But if we have an error code already, ignore this header
+        response.code = responsecode.INTERNAL_SERVER_ERROR
+        self.checkPreconditions(request, response, True, responsecode.INTERNAL_SERVER_ERROR)
+        response.code = responsecode.OK
+
+        # Weak tags okay for GET
+        out_headers.setHeader("ETag", http_headers.ETag('foo', weak=True))
+        request.headers.setRawHeaders("If-None-Match", ('W/"foo"',))
+        self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
+
+        # Weak tags not okay for other methods
+        request.method="PUT"
+        out_headers.setHeader("ETag", http_headers.ETag('foo', weak=True))
+        request.headers.setRawHeaders("If-None-Match", ('W/"foo"',))
+        self.checkPreconditions(request, response, True, responsecode.OK)
+
+    def testNoResponse(self):
+        # Ensure that passing etag/lastModified arguments instead of response works.
+        request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
+        request.method="PUT"
+        request.headers.setRawHeaders("If-None-Match", ('"foo"',))
+
+        self.checkPreconditions(request, None, True, responsecode.OK)
+        self.checkPreconditions(request, None, False, responsecode.PRECONDITION_FAILED,
+                                etag=http_headers.ETag('foo'),
+                                lastModified=946771200)
+
+        # Make sure that, while you shoudn't do this, that it doesn't cause an error
+        request.method="GET"
+        self.checkPreconditions(request, None, False, responsecode.NOT_MODIFIED,
+                                etag=http_headers.ETag('foo'))
+
+class IfRangeTestCase(unittest.TestCase):
+    def testIfRange(self):
+        request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
+        response = TestResponse()
+
+        self.assertEquals(http.checkIfRange(request, response), True)
+
+        request.headers.setRawHeaders("If-Range", ('"foo"',))
+        self.assertEquals(http.checkIfRange(request, response), False)
+
+        response.headers.setHeader("ETag", http_headers.ETag('foo'))
+        self.assertEquals(http.checkIfRange(request, response), True)
+
+        request.headers.setRawHeaders("If-Range", ('"bar"',))
+        response.headers.setHeader("ETag", http_headers.ETag('foo'))
+        self.assertEquals(http.checkIfRange(request, response), False)
+
+        request.headers.setRawHeaders("If-Range", ('W/"foo"',))
+        response.headers.setHeader("ETag", http_headers.ETag('foo', weak=True))
+        self.assertEquals(http.checkIfRange(request, response), False)
+
+        request.headers.setRawHeaders("If-Range", ('"foo"',))
+        response.headers.removeHeader("ETag")
+        self.assertEquals(http.checkIfRange(request, response), False)
+
+        request.headers.setRawHeaders("If-Range", ('Sun, 02 Jan 2000 00:00:00 GMT',))
+        response.headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
+        self.assertEquals(http.checkIfRange(request, response), True)
+
+        request.headers.setRawHeaders("If-Range", ('Sun, 02 Jan 2000 00:00:01 GMT',))
+        response.headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
+        self.assertEquals(http.checkIfRange(request, response), False)
+
+        request.headers.setRawHeaders("If-Range", ('Sun, 01 Jan 2000 23:59:59 GMT',))
+        response.headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
+        self.assertEquals(http.checkIfRange(request, response), False)
+
+        request.headers.setRawHeaders("If-Range", ('Sun, 01 Jan 2000 23:59:59 GMT',))
+        response.headers.removeHeader("Last-Modified")
+        self.assertEquals(http.checkIfRange(request, response), False)
+
+        request.headers.setRawHeaders("If-Range", ('jwerlqjL#$Y*KJAN',))
+        self.assertEquals(http.checkIfRange(request, response), False)
+
+
+
+class LoopbackRelay(loopback.LoopbackRelay):
+    implements(interfaces.IProducer)
+
+    def pauseProducing(self):
+        self.paused = True
+
+    def resumeProducing(self):
+        self.paused = False
+
+    def stopProducing(self):
+        self.loseConnection()
+
+    def loseWriteConnection(self):
+        # HACK.
+        self.loseConnection()
+
+class TestRequest(http.Request):
+    def __init__(self, *args, **kwargs):
+        http.Request.__init__(self, *args, **kwargs)
+        self.cmds = []
+        headers = list(self.headers.getAllRawHeaders())
+        headers.sort()
+        self.cmds.append(('init', self.method, self.uri, self.clientproto, self.stream.length, tuple(headers)))
+
+    def process(self):
+        pass
+    def handleContentChunk(self, data):
+        self.cmds.append(('contentChunk', data))
+
+    def handleContentComplete(self):
+        self.cmds.append(('contentComplete',))
+
+    def connectionLost(self, reason):
+        self.cmds.append(('connectionLost', reason))
+
+    def _finished(self, x):
+        self._reallyFinished(x)
+
+class TestResponse(object):
+    implements(iweb.IResponse)
+
+    code = responsecode.OK
+    headers = None
+
+    def __init__(self):
+        self.headers = http_headers.Headers()
+        self.stream = stream.ProducerStream()
+
+    def write(self, data):
+        self.stream.write(data)
+
+    def finish(self):
+        self.stream.finish()
+
+class TestClient(protocol.Protocol):
+    data = ""
+    done = False
+
+    def dataReceived(self, data):
+        self.data+=data
+
+    def write(self, data):
+        self.transport.write(data)
+
+    def connectionLost(self, reason):
+        self.done = True
+        self.transport.loseConnection()
+
+    def loseConnection(self):
+        self.done = True
+        self.transport.loseConnection()
+
+class TestConnection:
+    def __init__(self):
+        self.requests = []
+        self.client = None
+        self.callLaters = []
+
+    def fakeCallLater(self, secs, f):
+        assert secs == 0
+        self.callLaters.append(f)
+
+class HTTPTests(unittest.TestCase):
+    def connect(self, logFile=None, **protocol_kwargs):
+        cxn = TestConnection()
+
+        def makeTestRequest(*args):
+            cxn.requests.append(TestRequest(*args))
+            return cxn.requests[-1]
+
+        factory = channel.HTTPFactory(requestFactory=makeTestRequest,
+                                      _callLater=cxn.fakeCallLater,
+                                      **protocol_kwargs)
+
+        cxn.client = TestClient()
+        cxn.server = factory.buildProtocol(address.IPv4Address('TCP', '127.0.0.1', 2345))
+
+        cxn.serverToClient = LoopbackRelay(cxn.client, logFile)
+        cxn.clientToServer = LoopbackRelay(cxn.server, logFile)
+        cxn.server.makeConnection(cxn.serverToClient)
+        cxn.client.makeConnection(cxn.clientToServer)
+
+        return cxn
+
+    def iterate(self, cxn):
+        callLaters = cxn.callLaters
+        cxn.callLaters = []
+        for f in callLaters:
+            f()
+        cxn.serverToClient.clearBuffer()
+        cxn.clientToServer.clearBuffer()
+        if cxn.serverToClient.shouldLose:
+            cxn.serverToClient.clearBuffer()
+        if cxn.clientToServer.shouldLose:
+            cxn.clientToServer.clearBuffer()
+
+    def compareResult(self, cxn, cmds, data):
+        self.iterate(cxn)
+        for receivedRequest, expectedCommands in map(None, cxn.requests, cmds):
+            sortedHeaderCommands = []
+            for cmd in expectedCommands:
+                if len(cmd) == 6:
+                    sortedHeaders = list(cmd[5])
+                    sortedHeaders.sort()
+                    sortedHeaderCommands.append(cmd[:5] + (tuple(sortedHeaders),))
+                else:
+                    sortedHeaderCommands.append(cmd)
+            self.assertEquals(receivedRequest.cmds, sortedHeaderCommands)
+        self.assertEquals(cxn.client.data, data)
+
+    def assertDone(self, cxn, done=True):
+        self.iterate(cxn)
+        self.assertEquals(cxn.client.done, done)
+
+
+class CoreHTTPTestCase(HTTPTests):
+    # Note: these tests compare the client output using string
+    #       matching. It is acceptable for this to change and break
+    #       the test if you know what you are doing.
+
+    def testHTTP0_9(self, nouri=False):
+        cxn = self.connect()
+        cmds = [[]]
+        data = ""
+
+        if nouri:
+            cxn.client.write("GET\r\n")
+        else:
+            cxn.client.write("GET /\r\n")
+        # Second request which should not be handled
+        cxn.client.write("GET /two\r\n")
+
+        cmds[0] += [('init', 'GET', '/', (0,9), 0, ()), ('contentComplete',)]
+        self.compareResult(cxn, cmds, data)
+
+        response = TestResponse()
+        response.headers.setRawHeaders("Yo", ("One", "Two"))
+        cxn.requests[0].writeResponse(response)
+        response.write("")
+
+        self.compareResult(cxn, cmds, data)
+
+        response.write("Output")
+        data += "Output"
+        self.compareResult(cxn, cmds, data)
+
+        response.finish()
+        self.compareResult(cxn, cmds, data)
+
+        self.assertDone(cxn)
+
+    def testHTTP0_9_nouri(self):
+        self.testHTTP0_9(True)
+
+    def testHTTP1_0(self):
+        cxn = self.connect()
+        cmds = [[]]
+        data = ""
+
+        cxn.client.write("GET / HTTP/1.0\r\nContent-Length: 5\r\nHost: localhost\r\n\r\nInput")
+        # Second request which should not be handled
+        cxn.client.write("GET /two HTTP/1.0\r\n\r\n")
+
+        cmds[0] += [('init', 'GET', '/', (1,0), 5,
+                     (('Host', ['localhost']),)),
+                    ('contentChunk', 'Input'),
+                    ('contentComplete',)]
+        self.compareResult(cxn, cmds, data)
+
+        response = TestResponse()
+        response.headers.setRawHeaders("Yo", ("One", "Two"))
+        cxn.requests[0].writeResponse(response)
+        response.write("")
+
+        data += "HTTP/1.1 200 OK\r\nYo: One\r\nYo: Two\r\nConnection: close\r\n\r\n"
+        self.compareResult(cxn, cmds, data)
+
+        response.write("Output")
+        data += "Output"
+        self.compareResult(cxn, cmds, data)
+
+        response.finish()
+        self.compareResult(cxn, cmds, data)
+
+        self.assertDone(cxn)
+
+    def testHTTP1_0_keepalive(self):
+        cxn = self.connect()
+        cmds = [[]]
+        data = ""
+
+        cxn.client.write("GET / HTTP/1.0\r\nConnection: keep-alive\r\nContent-Length: 5\r\nHost: localhost\r\n\r\nInput")
+        cxn.client.write("GET /two HTTP/1.0\r\n\r\n")
+        # Third request shouldn't be handled
+        cxn.client.write("GET /three HTTP/1.0\r\n\r\n")
+
+        cmds[0] += [('init', 'GET', '/', (1,0), 5,
+                     (('Host', ['localhost']),)),
+                    ('contentChunk', 'Input'),
+                    ('contentComplete',)]
+        self.compareResult(cxn, cmds, data)
+
+        response0 = TestResponse()
+        response0.headers.setRawHeaders("Content-Length", ("6", ))
+        response0.headers.setRawHeaders("Yo", ("One", "Two"))
+        cxn.requests[0].writeResponse(response0)
+        response0.write("")
+
+        data += "HTTP/1.1 200 OK\r\nContent-Length: 6\r\nYo: One\r\nYo: Two\r\nConnection: Keep-Alive\r\n\r\n"
+        self.compareResult(cxn, cmds, data)
+
+        response0.write("Output")
+        data += "Output"
+        self.compareResult(cxn, cmds, data)
+
+        response0.finish()
+
+        # Now for second request:
+        cmds.append([])
+        cmds[1] += [('init', 'GET', '/two', (1,0), 0, ()), 
+                    ('contentComplete',)]
+        self.compareResult(cxn, cmds, data)
+
+
+        response1 = TestResponse()
+        response1.headers.setRawHeaders("Content-Length", ("0", ))
+        cxn.requests[1].writeResponse(response1)
+        response1.write("")
+
+        data += "HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
+        self.compareResult(cxn, cmds, data)
+        response1.finish()
+
+        self.assertDone(cxn)
+
+    def testHTTP1_1_pipelining(self):
+        cxn = self.connect(maxPipeline=2)
+        cmds = []
+        data = ""
+
+        # Both these show up immediately.
+        cxn.client.write("GET / HTTP/1.1\r\nContent-Length: 5\r\nHost: localhost\r\n\r\nInput")
+        cxn.client.write("GET /two HTTP/1.1\r\nHost: localhost\r\n\r\n")
+        # Doesn't show up until the first is done.
+        cxn.client.write("GET /three HTTP/1.1\r\nHost: localhost\r\n\r\n")
+        # Doesn't show up until the second is done.
+        cxn.client.write("GET /four HTTP/1.1\r\nHost: localhost\r\n\r\n")
+
+        cmds.append([])
+        cmds[0] += [('init', 'GET', '/', (1,1), 5, 
+                     (('Host', ['localhost']),)),
+                    ('contentChunk', 'Input'),
+                    ('contentComplete',)]
+        cmds.append([])
+        cmds[1] += [('init', 'GET', '/two', (1,1), 0, 
+                     (('Host', ['localhost']),)),
+                    ('contentComplete',)]
+
+        self.compareResult(cxn, cmds, data)
+
+        response0 = TestResponse()
+        response0.headers.setRawHeaders("Content-Length", ("6", ))
+        cxn.requests[0].writeResponse(response0)
+        response0.write("")
+
+        data += "HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\n"
+        self.compareResult(cxn, cmds, data)
+
+        response0.write("Output")
+        data += "Output"
+        self.compareResult(cxn, cmds, data)
+
+        response0.finish()
+
+        # Now the third request gets read:
+        cmds.append([])
+        cmds[2] += [('init', 'GET', '/three', (1,1), 0,
+                     (('Host', ['localhost']),)),
+                    ('contentComplete',)]
+        self.compareResult(cxn, cmds, data)
+
+        # Let's write out the third request before the second.
+        # This should not cause anything to be written to the client.
+        response2 = TestResponse()
+        response2.headers.setRawHeaders("Content-Length", ("5", ))
+        cxn.requests[2].writeResponse(response2)
+
+        response2.write("Three")
+        response2.finish()
+
+        self.compareResult(cxn, cmds, data)
+
+        response1 = TestResponse()
+        response1.headers.setRawHeaders("Content-Length", ("3", ))
+        cxn.requests[1].writeResponse(response1)
+        response1.write("Two")
+
+        data += "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nTwo"
+        self.compareResult(cxn, cmds, data)
+
+        response1.finish()
+
+        # Fourth request shows up
+        cmds.append([])
+        cmds[3] += [('init', 'GET', '/four', (1,1), 0,
+                     (('Host', ['localhost']),)),
+                    ('contentComplete',)]
+        data += "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nThree"
+        self.compareResult(cxn, cmds, data)
+
+        response3 = TestResponse()
+        response3.headers.setRawHeaders("Content-Length", ("0",))
+        cxn.requests[3].writeResponse(response3)
+        response3.finish()
+
+        data += "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
+        self.compareResult(cxn, cmds, data)
+
+        self.assertDone(cxn, done=False)
+        cxn.client.loseConnection()
+        self.assertDone(cxn)
+
+    def testHTTP1_1_chunking(self):
+        cxn = self.connect()
+        cmds = [[]]
+        data = ""
+        cxn.client.write("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\nHost: localhost\r\n\r\n5\r\nInput\r\n")
+
+        cmds[0] += [('init', 'GET', '/', (1,1), None,
+                     (('Host', ['localhost']),)),
+                    ('contentChunk', 'Input')]
+
+        self.compareResult(cxn, cmds, data)
+
+        cxn.client.write("1; blahblahblah\r\na\r\n10\r\nabcdefghijklmnop\r\n")
+        cmds[0] += [('contentChunk', 'a'),('contentChunk', 'abcdefghijklmnop')]
+        self.compareResult(cxn, cmds, data)
+
+        cxn.client.write("0\r\nRandom-Ignored-Trailer: foo\r\n\r\n")
+        cmds[0] += [('contentComplete',)]
+        self.compareResult(cxn, cmds, data)
+
+        response = TestResponse()
+        cxn.requests[0].writeResponse(response)
+        response.write("Output")
+        data += "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nOutput\r\n"
+        self.compareResult(cxn, cmds, data)
+
+        response.write("blahblahblah")
+        data += "C\r\nblahblahblah\r\n"
+        self.compareResult(cxn, cmds, data)
+
+        response.finish()
+        data += "0\r\n\r\n"
+        self.compareResult(cxn, cmds, data)
+
+        cxn.client.loseConnection()
+        self.assertDone(cxn)
+
+    def testHTTP1_1_expect_continue(self):
+        cxn = self.connect()
+        cmds = [[]]
+        data = ""
+        cxn.client.write("GET / HTTP/1.1\r\nContent-Length: 5\r\nHost: localhost\r\nExpect: 100-continue\r\n\r\n")
+        cmds[0] += [('init', 'GET', '/', (1,1), 5,
+                     (('Expect', ['100-continue']), ('Host', ['localhost'])))]
+        self.compareResult(cxn, cmds, data)
+
+        cxn.requests[0].stream.read()
+        data += "HTTP/1.1 100 Continue\r\n\r\n"
+        self.compareResult(cxn, cmds, data)
+
+        cxn.client.write("Input")
+        cmds[0] += [('contentChunk', 'Input'),
+                    ('contentComplete',)]
+        self.compareResult(cxn, cmds, data)
+
+        response = TestResponse()
+        response.headers.setRawHeaders("Content-Length", ("6",))
+        cxn.requests[0].writeResponse(response)
+        response.write("Output")
+        response.finish()
+
+        data += "HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nOutput"
+        self.compareResult(cxn, cmds, data)
+
+        cxn.client.loseConnection()
+        self.assertDone(cxn)
+
+    def testHTTP1_1_expect_continue_early_reply(self):
+        cxn = self.connect()
+        cmds = [[]]
+        data = ""
+        cxn.client.write("GET / HTTP/1.1\r\nContent-Length: 5\r\nHost: localhost\r\nExpect: 100-continue\r\n\r\n")
+        cmds[0] += [('init', 'GET', '/', (1,1), 5,
+                     (('Host', ['localhost']), ('Expect', ['100-continue'])))]
+        self.compareResult(cxn, cmds, data)
+
+        response = TestResponse()
+        response.headers.setRawHeaders("Content-Length", ("6",))
+        cxn.requests[0].writeResponse(response)
+        response.write("Output")
+        response.finish()
+
+        cmds[0] += [('contentComplete',)]
+        data += "HTTP/1.1 200 OK\r\nContent-Length: 6\r\nConnection: close\r\n\r\nOutput"
+        self.compareResult(cxn, cmds, data)
+
+        cxn.client.loseConnection()
+        self.assertDone(cxn)
+
+    def testHeaderContinuation(self):
+        cxn = self.connect()
+        cmds = [[]]
+        data = ""
+
+        cxn.client.write("GET / HTTP/1.1\r\nHost: localhost\r\nFoo: yada\r\n yada\r\n\r\n")
+        cmds[0] += [('init', 'GET', '/', (1,1), 0,
+                     (('Host', ['localhost']), ('Foo', ['yada yada']),)),
+                    ('contentComplete',)]
+        self.compareResult(cxn, cmds, data)
+
+        cxn.client.loseConnection()
+        self.assertDone(cxn)
+
+    def testTimeout_immediate(self):
+        # timeout 0 => timeout on first iterate call
+        cxn = self.connect(inputTimeOut = 0)
+        return deferLater(reactor, 0, self.assertDone, cxn)
+
+    def testTimeout_inRequest(self):
+        cxn = self.connect(inputTimeOut = 0.3)
+        cmds = [[]]
+        data = ""
+
+        cxn.client.write("GET / HTTP/1.1\r\n")
+        return deferLater(reactor, 0.5, self.assertDone, cxn)
+
+    def testTimeout_betweenRequests(self):
+        cxn = self.connect(betweenRequestsTimeOut = 0.3)
+        cmds = [[]]
+        data = ""
+
+        cxn.client.write("GET / HTTP/1.1\r\n\r\n")
+        cmds[0] += [('init', 'GET', '/', (1,1), 0, ()),
+                    ('contentComplete',)]
+        self.compareResult(cxn, cmds, data)
+
+        response = TestResponse()
+        response.headers.setRawHeaders("Content-Length", ("0",))
+        cxn.requests[0].writeResponse(response)
+        response.finish()
+
+        data += "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
+
+        self.compareResult(cxn, cmds, data)
+        return deferLater(reactor, 0.5, self.assertDone, cxn) # Wait for timeout
+
+    def testConnectionCloseRequested(self):
+        cxn = self.connect()
+        cmds = [[]]
+        data = ""
+
+        cxn.client.write("GET / HTTP/1.1\r\n\r\n")
+        cmds[0] += [('init', 'GET', '/', (1,1), 0, ()),
+                    ('contentComplete',)]
+        self.compareResult(cxn, cmds, data)
+
+        cxn.client.write("GET / HTTP/1.1\r\nConnection: close\r\n\r\n")
+        cmds.append([])
+        cmds[1] += [('init', 'GET', '/', (1,1), 0, ()),
+                    ('contentComplete',)]
+        self.compareResult(cxn, cmds, data)
+
+        response = TestResponse()
+        response.headers.setRawHeaders("Content-Length", ("0",))
+        cxn.requests[0].writeResponse(response)
+        response.finish()
+
+        data += "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
+
+        response = TestResponse()
+        response.headers.setRawHeaders("Content-Length", ("0",))
+        cxn.requests[1].writeResponse(response)
+        response.finish()
+
+        data += "HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
+
+        self.compareResult(cxn, cmds, data)
+        self.assertDone(cxn)
+
+    def testExtraCRLFs(self):
+        cxn = self.connect()
+        cmds = [[]]
+        data = ""
+
+        # Some broken clients (old IEs) send an extra CRLF after post
+        cxn.client.write("POST / HTTP/1.1\r\nContent-Length: 5\r\nHost: localhost\r\n\r\nInput\r\n")
+        cmds[0] += [('init', 'POST', '/', (1,1), 5,
+                     (('Host', ['localhost']),)),
+                    ('contentChunk', 'Input'),
+                    ('contentComplete',)]
+
+        self.compareResult(cxn, cmds, data)
+
+        cxn.client.write("GET /two HTTP/1.1\r\n\r\n")
+        cmds.append([])
+        cmds[1] += [('init', 'GET', '/two', (1,1), 0, ()),
+                    ('contentComplete',)]
+        self.compareResult(cxn, cmds, data)
+
+        cxn.client.loseConnection()
+        self.assertDone(cxn)
+
+    def testDisallowPersistentConnections(self):
+        cxn = self.connect(allowPersistentConnections=False)
+        cmds = [[]]
+        data = ""
+
+        cxn.client.write("GET / HTTP/1.1\r\nHost: localhost\r\n\r\nGET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
+        cmds[0] += [('init', 'GET', '/', (1,1), 0,
+                     (('Host', ['localhost']),)),
+                    ('contentComplete',)]
+        self.compareResult(cxn, cmds, data)
+
+        response = TestResponse()
+        response.finish()
+        cxn.requests[0].writeResponse(response)
+        data += 'HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n'
+        self.compareResult(cxn, cmds, data)
+        self.assertDone(cxn)
+
+    def testIgnoreBogusContentLength(self):
+        # Ensure that content-length is ignored when transfer-encoding
+        # is also specified.
+        cxn = self.connect()
+        cmds = [[]]
+        data = ""
+        cxn.client.write("GET / HTTP/1.1\r\nContent-Length: 100\r\nTransfer-Encoding: chunked\r\nHost: localhost\r\n\r\n5\r\nInput\r\n")
+
+        cmds[0] += [('init', 'GET', '/', (1,1), None,
+                     (('Host', ['localhost']),)),
+                    ('contentChunk', 'Input')]
+
+        self.compareResult(cxn, cmds, data)
+
+        cxn.client.write("0\r\n\r\n")
+        cmds[0] += [('contentComplete',)]
+        self.compareResult(cxn, cmds, data)
+
+        response = TestResponse()
+        response.finish()
+        cxn.requests[0].writeResponse(response)
+        data += "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
+        self.compareResult(cxn, cmds, data)
+
+        cxn.client.loseConnection()
+        self.assertDone(cxn)
+
+class ErrorTestCase(HTTPTests):
+    def assertStartsWith(self, first, second, msg=None):
+        self.assert_(first.startswith(second), '%r.startswith(%r)' % (first, second))
+
+    def checkError(self, cxn, code):
+        self.iterate(cxn)
+        self.assertStartsWith(cxn.client.data, "HTTP/1.1 %d "%code)
+        self.assertIn("\r\nConnection: close\r\n", cxn.client.data)
+        # Ensure error messages have a defined content-length.
+        self.assertIn("\r\nContent-Length:", cxn.client.data)
+        self.assertDone(cxn)
+
+    def testChunkingError1(self):
+        cxn = self.connect()
+        cxn.client.write("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\nasdf\r\n")
+
+        self.checkError(cxn, 400)
+
+    def testChunkingError2(self):
+        cxn = self.connect()
+        cxn.client.write("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nblahblah\r\n")
+
+        self.checkError(cxn, 400)
+
+    def testChunkingError3(self):
+        cxn = self.connect()
+        cxn.client.write("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n-1\r\nasdf\r\n")
+
+        self.checkError(cxn, 400)
+
+    def testTooManyHeaders(self):
+        cxn = self.connect()
+        cxn.client.write("GET / HTTP/1.1\r\n")
+        cxn.client.write("Foo: Bar\r\n"*5000)
+
+        self.checkError(cxn, 400)
+
+    def testLineTooLong(self):
+        cxn = self.connect()
+        cxn.client.write("GET / HTTP/1.1\r\n")
+        cxn.client.write("Foo: "+("Bar"*10000))
+
+        self.checkError(cxn, 400)
+
+    def testLineTooLong2(self):
+        cxn = self.connect()
+        cxn.client.write("GET "+("/Bar")*10000 +" HTTP/1.1\r\n")
+
+        self.checkError(cxn, 414)
+
+    def testNoColon(self):
+        cxn = self.connect()
+        cxn.client.write("GET / HTTP/1.1\r\n")
+        cxn.client.write("Blahblah\r\n\r\n")
+
+        self.checkError(cxn, 400)
+
+    def testBadRequest(self):
+        cxn = self.connect()
+        cxn.client.write("GET / more HTTP/1.1\r\n")
+
+        self.checkError(cxn, 400)
+
+    def testWrongProtocol(self):
+        cxn = self.connect()
+        cxn.client.write("GET / Foobar/1.0\r\n")
+
+        self.checkError(cxn, 400)
+
+    def testBadProtocolVersion(self):
+        cxn = self.connect()
+        cxn.client.write("GET / HTTP/1\r\n")
+
+        self.checkError(cxn, 400)
+
+    def testBadProtocolVersion2(self):
+        cxn = self.connect()
+        cxn.client.write("GET / HTTP/-1.0\r\n")
+
+        self.checkError(cxn, 400)
+
+    def testWrongProtocolVersion(self):
+        cxn = self.connect()
+        cxn.client.write("GET / HTTP/2.0\r\n")
+
+        self.checkError(cxn, 505)
+
+    def testUnsupportedTE(self):
+        cxn = self.connect()
+        cxn.client.write("GET / HTTP/1.1\r\n")
+        cxn.client.write("Transfer-Encoding: blahblahblah, chunked\r\n\r\n")
+        self.checkError(cxn, 501)
+
+    def testTEWithoutChunked(self):
+        cxn = self.connect()
+        cxn.client.write("GET / HTTP/1.1\r\n")
+        cxn.client.write("Transfer-Encoding: gzip\r\n\r\n")
+        self.checkError(cxn, 400)
+
+class PipelinedErrorTestCase(ErrorTestCase):
+    # Make sure that even low level reading errors don't corrupt the data stream,
+    # but always wait until their turn to respond.
+
+    def connect(self):
+        cxn = ErrorTestCase.connect(self)
+        cxn.client.write("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
+
+        cmds = [[('init', 'GET', '/', (1,1), 0,
+                 (('Host', ['localhost']),)),
+                ('contentComplete', )]]
+        data = ""
+        self.compareResult(cxn, cmds, data)
+        return cxn
+
+    def checkError(self, cxn, code):
+        self.iterate(cxn)
+        self.assertEquals(cxn.client.data, '')
+
+        response = TestResponse()
+        response.headers.setRawHeaders("Content-Length", ("0",))
+        cxn.requests[0].writeResponse(response)
+        response.write('')
+
+        data = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
+        self.iterate(cxn)
+        self.assertEquals(cxn.client.data, data)
+
+        # Reset the data so the checkError's startswith test can work right.
+        cxn.client.data = ""
+
+        response.finish()
+        ErrorTestCase.checkError(self, cxn, code)
+
+
+class SimpleFactory(channel.HTTPFactory):
+    def buildProtocol(self, addr):
+        # Do a bunch of crazy crap just so that the test case can know when the
+        # connection is done.
+        p = channel.HTTPFactory.buildProtocol(self, addr)
+        cl = p.connectionLost
+        def newCl(reason):
+            reactor.callLater(0, lambda: self.testcase.connlost.callback(None))
+            return cl(reason)
+        p.connectionLost = newCl
+        self.conn = p
+        return p
+
+class SimpleRequest(http.Request):
+    def process(self):
+        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)
+        response.finish()
+        self.writeResponse(response)
+
+class AbstractServerTestMixin:
+    type = None
+    def testBasicWorkingness(self):
+        args = ('-u', util.sibpath(__file__, "simple_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 402 Payment Required\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")
+    testBasicWorkingness = deferredGenerator(testBasicWorkingness)
+
+    def testLingeringClose(self):
+        args = ('-u', util.sibpath(__file__, "simple_client.py"),
+                "lingeringClose", 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 402 Payment Required\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")
+    testLingeringClose = deferredGenerator(testLingeringClose)
+
+class TCPServerTest(unittest.TestCase, AbstractServerTestMixin):
+    type = 'tcp'
+    def setUp(self):
+        factory=SimpleFactory(requestFactory=SimpleRequest)
+
+        factory.testcase = self
+        self.factory = factory
+        self.connlost = defer.Deferred()
+
+        self.socket = reactor.listenTCP(0, factory)
+        self.port = self.socket.getHost().port
+
+    def tearDown(self):
+        # Make sure the listening port is closed
+        d = defer.maybeDeferred(self.socket.stopListening)
+
+        def finish(v):
+            # And make sure the established connection is, too
+            self.factory.conn.transport.loseConnection()
+            return self.connlost
+        return d.addCallback(finish)
+
+
+try:
+    from twisted.internet import ssl
+except ImportError:
+   # happens the first time the interpreter tries to import it
+   ssl = None
+if ssl and not ssl.supported:
+   # happens second and later times
+   ssl = None
+
+certPath = util.sibpath(__file__, "server.pem")
+
+class SSLServerTest(unittest.TestCase, AbstractServerTestMixin):
+    type = 'ssl'
+    def setUp(self):
+        sCTX = ssl.DefaultOpenSSLContextFactory(certPath, certPath)
+        factory=SimpleFactory(requestFactory=SimpleRequest)
+
+        factory.testcase = self
+        self.factory = factory
+        self.connlost = defer.Deferred()
+
+        self.socket = reactor.listenSSL(0, factory, sCTX)
+        self.port = self.socket.getHost().port
+
+    def tearDown(self):
+        # Make sure the listening port is closed
+        d = defer.maybeDeferred(self.socket.stopListening)
+
+        def finish(v):
+            # And make sure the established connection is, too
+            self.factory.conn.transport.loseConnection()
+            return self.connlost
+        return d.addCallback(finish)
+
+    def testLingeringClose(self):
+        return super(SSLServerTest, self).testLingeringClose()
+
+    if runtime.platform.isWindows():
+        # This may not just be Windows, but all platforms with more recent
+        # versions of OpenSSL.  Do some more experimentation...
+        testLingeringClose.todo = "buffering kills the connection too early; test this some other way"
+
+
+if interfaces.IReactorProcess(reactor, None) is None:
+    TCPServerTest.skip = SSLServerTest.skip = "Required process support missing from reactor"
+elif interfaces.IReactorSSL(reactor, None) is None:
+    SSLServerTest.skip = "Required SSL support missing from reactor"
+elif ssl is None:
+    SSLServerTest.skip = "SSL not available, cannot test SSL."

Deleted: CalendarServer/trunk/twext/web2/test/test_http_headers.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_http_headers.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/test/test_http_headers.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,796 +0,0 @@
-# Copyright (c) 2008 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Tests for L{twext.web2.http_headers}.
-"""
-
-from twisted.trial import unittest
-import random, time
-
-from twext.web2 import http_headers
-from twext.web2.http_headers import Cookie, HeaderHandler, quoteString, generateKeyValues
-from twisted.python import util
-
-class parsedvalue:
-    """Marker class"""
-    def __init__(self, raw):
-        self.raw = raw
-
-    def __eq__(self, other):
-        return isinstance(other, parsedvalue) and other.raw == self.raw
-
-class HeadersAPITest(unittest.TestCase):
-    """Make sure the public API exists and works."""
-    def testRaw(self):
-        rawvalue = ("value1", "value2")
-        h = http_headers.Headers(handler=HeaderHandler(parsers={}, generators={}))
-        h.setRawHeaders("test", rawvalue)
-        self.assertEquals(h.hasHeader("test"), True)
-        self.assertEquals(h.getRawHeaders("test"), rawvalue)
-        self.assertEquals(list(h.getAllRawHeaders()), [('Test', rawvalue)])
-
-        self.assertEquals(h.getRawHeaders("foobar"), None)
-        h.removeHeader("test")
-        self.assertEquals(h.getRawHeaders("test"), None)
-
-    def testParsed(self):
-        parsed = parsedvalue(("value1", "value2"))
-        h = http_headers.Headers(handler=HeaderHandler(parsers={}, generators={}))
-
-        h.setHeader("test", parsed)
-        self.assertEquals(h.hasHeader("test"), True)
-        self.assertEquals(h.getHeader("test"), parsed)
-
-        self.assertEquals(h.getHeader("foobar"), None)
-        h.removeHeader("test")
-        self.assertEquals(h.getHeader("test"), None)
-
-    def testParsedAndRaw(self):
-        def parse(raw):
-            return parsedvalue(raw)
-
-        def generate(parsed):
-            return parsed.raw
-
-        rawvalue = ("value1", "value2")
-        rawvalue2 = ("value3", "value4")
-        handler = HeaderHandler(parsers={'test':(parse,)},
-                                generators={'test':(generate,)})
-
-        h = http_headers.Headers(handler=handler)
-        h.setRawHeaders("test", rawvalue)
-        self.assertEquals(h.getHeader("test"), parsedvalue(rawvalue))
-
-        h.setHeader("test", parsedvalue(rawvalue2))
-        self.assertEquals(h.getRawHeaders("test"), rawvalue2)
-
-        # Check the initializers
-        h = http_headers.Headers(rawHeaders={"test": rawvalue},
-                                 handler=handler)
-        self.assertEquals(h.getHeader("test"), parsedvalue(rawvalue))
-
-        h = http_headers.Headers({"test": parsedvalue(rawvalue2)},
-                                 handler=handler)
-        self.assertEquals(h.getRawHeaders("test"), rawvalue2)
-
-    def testImmutable(self):
-        h = http_headers.Headers(handler=HeaderHandler(parsers={}, generators={}))
-
-        h.makeImmutable()
-        self.assertRaises(AttributeError, h.setRawHeaders, "test", [1])
-        self.assertRaises(AttributeError, h.setHeader, "test", 1)
-        self.assertRaises(AttributeError, h.removeHeader, "test")
-
-class TokenizerTest(unittest.TestCase):
-    """Test header list parsing functions."""
-
-    def testParse(self):
-        parser = lambda val: list(http_headers.tokenize([val,]))
-        Token = http_headers.Token
-        tests = (('foo,bar', ['foo', Token(','), 'bar']),
-                 ('FOO,BAR', ['foo', Token(','), 'bar']),
-                 (' \t foo  \t bar  \t  ,  \t baz   ', ['foo', Token(' '), 'bar', Token(','), 'baz']),
-                 ('()<>@,;:\\/[]?={}', [Token('('), Token(')'), Token('<'), Token('>'), Token('@'), Token(','), Token(';'), Token(':'), Token('\\'), Token('/'), Token('['), Token(']'), Token('?'), Token('='), Token('{'), Token('}')]),
-                 (' "foo" ', ['foo']),
-                 ('"FOO(),\\"BAR,"', ['FOO(),"BAR,']))
-
-        raiseTests = ('"open quote', '"ending \\', "control character: \x127", "\x00", "\x1f")
-
-        for test,result in tests:
-            self.assertEquals(parser(test), result)
-        for test in raiseTests:
-            self.assertRaises(ValueError, parser, test)
-
-    def testGenerate(self):
-        pass
-
-    def testRoundtrip(self):
-        pass
-
-def atSpecifiedTime(when, func):
-    def inner(*a, **kw):
-        orig = time.time
-        time.time = lambda: when
-        try:
-            return func(*a, **kw)
-        finally:
-            time.time = orig
-    return util.mergeFunctionMetadata(func, inner)
-
-def parseHeader(name, val):
-    head = http_headers.Headers(handler=http_headers.DefaultHTTPHandler)
-    head.setRawHeaders(name,val)
-    return head.getHeader(name)
-parseHeader = atSpecifiedTime(999999990, parseHeader) # Sun, 09 Sep 2001 01:46:30 GMT
-
-def generateHeader(name, val):
-    head = http_headers.Headers(handler=http_headers.DefaultHTTPHandler)
-    head.setHeader(name, val)
-    return head.getRawHeaders(name)
-generateHeader = atSpecifiedTime(999999990, generateHeader) # Sun, 09 Sep 2001 01:46:30 GMT
-
-
-class HeaderParsingTestBase(unittest.TestCase):
-    def runRoundtripTest(self, headername, table):
-        """
-        Perform some assertions about the behavior of parsing and
-        generating HTTP headers.  Specifically: parse an HTTP header
-        value, assert that the parsed form contains all the available
-        information with the correct structure; generate the HTTP
-        header value from the parsed form, assert that it contains
-        certain literal strings; finally, re-parse the generated HTTP
-        header value and assert that the resulting structured data is
-        the same as the first-pass parsed form.
-
-        @type headername: C{str}
-        @param headername: The name of the HTTP header L{table} contains values for.
-
-        @type table: A sequence of tuples describing inputs to and
-        outputs from header parsing and generation.  The tuples may be
-        either 2 or 3 elements long.  In either case: the first
-        element is a string representing an HTTP-format header value;
-        the second element is a dictionary mapping names of parameters
-        to values of those parameters (the parsed form of the header).
-        If there is a third element, it is a list of strings which
-        must occur exactly in the HTTP header value
-        string which is re-generated from the parsed form.
-        """
-        for row in table:
-            if len(row) == 2:
-                rawHeaderInput, parsedHeaderData = row
-                requiredGeneratedElements = []
-            elif len(row) == 3:
-                rawHeaderInput, parsedHeaderData, requiredGeneratedElements = row
-
-
-            assert isinstance(requiredGeneratedElements, list)
-
-            # parser
-            parsed = parseHeader(headername, [rawHeaderInput,])
-            self.assertEquals(parsed, parsedHeaderData)
-
-            regeneratedHeaderValue = generateHeader(headername, parsed)
-
-            if requiredGeneratedElements:
-                # generator
-                for regeneratedElement in regeneratedHeaderValue:
-                    reqEle = requiredGeneratedElements[regeneratedHeaderValue.index(regeneratedElement)]
-                    elementIndex = regeneratedElement.find(reqEle)
-                    self.assertNotEqual(
-                        elementIndex, -1,
-                        "%r did not appear in generated HTTP header %r: %r" % (reqEle,
-                                                                               headername,
-                                                                               regeneratedElement))
-
-            # parser/generator
-            reparsed = parseHeader(headername, regeneratedHeaderValue)
-            self.assertEquals(parsed, reparsed)
-
-
-    def invalidParseTest(self, headername, values):
-        for val in values:
-            parsed = parseHeader(headername, val)
-            self.assertEquals(parsed, None)
-
-class GeneralHeaderParsingTests(HeaderParsingTestBase):
-    def testCacheControl(self):
-        table = (
-            ("no-cache",
-             {'no-cache':None}),
-            ("no-cache, no-store, max-age=5, max-stale=3, min-fresh=5, no-transform, only-if-cached, blahblah-extension-thingy",
-             {'no-cache': None,
-              'no-store': None,
-              'max-age':5,
-              'max-stale':3,
-              'min-fresh':5,
-              'no-transform':None,
-              'only-if-cached':None,
-              'blahblah-extension-thingy':None}),
-            ("max-stale",
-             {'max-stale':None}),
-            ("public, private, no-cache, no-store, no-transform, must-revalidate, proxy-revalidate, max-age=5, s-maxage=10, blahblah-extension-thingy",
-             {'public':None,
-              'private':None,
-              'no-cache':None,
-              'no-store':None,
-              'no-transform':None,
-              'must-revalidate':None,
-              'proxy-revalidate':None,
-              'max-age':5,
-              's-maxage':10,
-              'blahblah-extension-thingy':None}),
-            ('private="Set-Cookie, Set-Cookie2", no-cache="PROXY-AUTHENTICATE"',
-             {'private': ['set-cookie', 'set-cookie2'],
-              'no-cache': ['proxy-authenticate']},
-             ['private="Set-Cookie, Set-Cookie2"', 'no-cache="Proxy-Authenticate"']),
-            )
-        self.runRoundtripTest("Cache-Control", table)
-
-    def testConnection(self):
-        table = (
-            ("close", ['close',]),
-            ("close, foo-bar", ['close', 'foo-bar'])
-            )
-        self.runRoundtripTest("Connection", table)
-
-    def testDate(self):
-        # Don't need major tests since the datetime parser has its own tests
-        self.runRoundtripTest("Date", (("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000),))
-
-#     def testPragma(self):
-#         fail
-
-#     def testTrailer(self):
-#         fail
-
-    def testTransferEncoding(self):
-        table = (
-            ('chunked', ['chunked']),
-            ('gzip, chunked', ['gzip', 'chunked'])
-            )
-        self.runRoundtripTest("Transfer-Encoding", table)
-
-#     def testUpgrade(self):
-#         fail
-
-#     def testVia(self):
-#         fail
-
-#     def testWarning(self):
-#         fail
-
-class RequestHeaderParsingTests(HeaderParsingTestBase):
-    #FIXME test ordering too.
-    def testAccept(self):
-        table = (
-            ("audio/*;q=0.2, audio/basic",
-             {http_headers.MimeType('audio', '*'): 0.2,
-              http_headers.MimeType('audio', 'basic'): 1.0}),
-
-            ("text/plain;q=0.5, text/html, text/x-dvi;q=0.8, text/x-c",
-             {http_headers.MimeType('text', 'plain'): 0.5,
-              http_headers.MimeType('text', 'html'): 1.0,
-              http_headers.MimeType('text', 'x-dvi'): 0.8,
-              http_headers.MimeType('text', 'x-c'): 1.0}),
-
-            ("text/*, text/html, text/html;level=1, */*",
-             {http_headers.MimeType('text', '*'): 1.0,
-              http_headers.MimeType('text', 'html'): 1.0,
-              http_headers.MimeType('text', 'html', (('level', '1'),)): 1.0,
-              http_headers.MimeType('*', '*'): 1.0}),
-
-       ("text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5",
-             {http_headers.MimeType('text', '*'): 0.3,
-              http_headers.MimeType('text', 'html'): 0.7,
-              http_headers.MimeType('text', 'html', (('level', '1'),)): 1.0,
-              http_headers.MimeType('text', 'html', (('level', '2'),)): 0.4,
-              http_headers.MimeType('*', '*'): 0.5}),
-            )
-
-        self.runRoundtripTest("Accept", table)
-
-
-    def testAcceptCharset(self):
-        table = (
-            ("iso-8859-5, unicode-1-1;q=0.8",
-             {'iso-8859-5': 1.0, 'iso-8859-1': 1.0, 'unicode-1-1': 0.8},
-             ["iso-8859-5", "unicode-1-1;q=0.8", "iso-8859-1"]),
-            ("iso-8859-1;q=0.7",
-             {'iso-8859-1': 0.7}),
-            ("*;q=.7",
-             {'*': 0.7},
-             ["*;q=0.7"]),
-            ("",
-             {'iso-8859-1': 1.0},
-             ["iso-8859-1"]), # Yes this is an actual change -- we'll say that's okay. :)
-            )
-        self.runRoundtripTest("Accept-Charset", table)
-
-    def testAcceptEncoding(self):
-        table = (
-            ("compress, gzip",
-             {'compress': 1.0, 'gzip': 1.0, 'identity': 0.0001}),
-            ("",
-             {'identity': 0.0001}),
-            ("*",
-             {'*': 1}),
-            ("compress;q=0.5, gzip;q=1.0",
-             {'compress': 0.5, 'gzip': 1.0, 'identity': 0.0001},
-             ["compress;q=0.5", "gzip"]),
-            ("gzip;q=1.0, identity;q=0.5, *;q=0",
-             {'gzip': 1.0, 'identity': 0.5, '*':0},
-             ["gzip", "identity;q=0.5", "*;q=0"]),
-            )
-        self.runRoundtripTest("Accept-Encoding", table)
-
-    def testAcceptLanguage(self):
-        table = (
-            ("da, en-gb;q=0.8, en;q=0.7",
-             {'da': 1.0, 'en-gb': 0.8, 'en': 0.7}),
-            ("*",
-             {'*': 1}),
-            )
-        self.runRoundtripTest("Accept-Language", table)
-
-    def testAuthorization(self):
-        table = (
-            ("Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
-             ("basic", "dXNlcm5hbWU6cGFzc3dvcmQ="),
-             ["basic dXNlcm5hbWU6cGFzc3dvcmQ="]),
-            ('Digest nonce="bar", realm="foo", username="baz", response="bax"',
-             ('digest', 'nonce="bar", realm="foo", username="baz", response="bax"'),
-             ['digest', 'nonce="bar"', 'realm="foo"', 'username="baz"', 'response="bax"'])
-            )
-
-        self.runRoundtripTest("Authorization", table)
-
-    def testCookie(self):
-        table = (
-            ('name=value', [Cookie('name', 'value')]),
-            ('"name"="value"', [Cookie('"name"', '"value"')]),
-            ('name,"blah=value,"', [Cookie('name,"blah', 'value,"')]),
-            ('name,"blah  = value,"  ', [Cookie('name,"blah', 'value,"')], ['name,"blah=value,"']),
-            ("`~!@#$%^&*()-_+[{]}\\|:'\",<.>/?=`~!@#$%^&*()-_+[{]}\\|:'\",<.>/?", [Cookie("`~!@#$%^&*()-_+[{]}\\|:'\",<.>/?", "`~!@#$%^&*()-_+[{]}\\|:'\",<.>/?")]),
-            ('name,"blah  = value,"  ; name2=val2',
-               [Cookie('name,"blah', 'value,"'), Cookie('name2', 'val2')],
-               ['name,"blah=value,"', 'name2=val2']),
-            )
-        self.runRoundtripTest("Cookie", table)
-
-        #newstyle RFC2965 Cookie
-        table2 = (
-            ('$Version="1";'
-             'name="value";$Path="/foo";$Domain="www.local";$Port="80,8000";'
-             'name2="value"',
-             [Cookie('name', 'value', path='/foo', domain='www.local', ports=(80,8000), version=1), Cookie('name2', 'value', version=1)]),
-            ('$Version="1";'
-             'name="value";$Port',
-             [Cookie('name', 'value', ports=(), version=1)]),
-            ('$Version = 1, NAME = "qq\\"qq",Frob=boo',
-             [Cookie('name', 'qq"qq', version=1), Cookie('frob', 'boo', version=1)],
-             ['$Version="1";name="qq\\"qq";frob="boo"']),
-            )
-        self.runRoundtripTest("Cookie", table2)
-
-        # Generate only!
-        # make headers by combining oldstyle and newstyle cookies
-        table3 = (
-            ([Cookie('name', 'value'), Cookie('name2', 'value2', version=1)],
-             '$Version="1";name=value;name2="value2"'),
-            ([Cookie('name', 'value', path="/foo"), Cookie('name2', 'value2', domain="bar.baz", version=1)],
-             '$Version="1";name=value;$Path="/foo";name2="value2";$Domain="bar.baz"'),
-            ([Cookie('invalid,"name', 'value'), Cookie('name2', 'value2', version=1)],
-             '$Version="1";name2="value2"'),
-            ([Cookie('name', 'qq"qq'), Cookie('name2', 'value2', version=1)],
-             '$Version="1";name="qq\\"qq";name2="value2"'),
-            )
-        for row in table3:
-            self.assertEquals(generateHeader("Cookie", row[0]), [row[1],])
-
-
-
-    def testSetCookie(self):
-        table = (
-            ('name,"blah=value,; expires=Sun, 09 Sep 2001 01:46:40 GMT; path=/foo; domain=bar.baz; secure',
-             [Cookie('name,"blah', 'value,', expires=1000000000, path="/foo", domain="bar.baz", secure=True)]),
-            ('name,"blah = value, ; expires="Sun, 09 Sep 2001 01:46:40 GMT"',
-             [Cookie('name,"blah', 'value,', expires=1000000000)],
-             ['name,"blah=value,', 'expires=Sun, 09 Sep 2001 01:46:40 GMT']),
-            )
-        self.runRoundtripTest("Set-Cookie", table)
-
-    def testSetCookie2(self):
-        table = (
-            ('name="value"; Comment="YadaYada"; CommentURL="http://frobnotz/"; Discard; Domain="blah.blah"; Max-Age=10; Path="/foo"; Port="80,8080"; Secure; Version="1"',
-             [Cookie("name", "value", comment="YadaYada", commenturl="http://frobnotz/", discard=True, domain="blah.blah", expires=1000000000, path="/foo", ports=(80,8080), secure=True, version=1)]),
-            )
-        self.runRoundtripTest("Set-Cookie2", table)
-
-    def testExpect(self):
-        table = (
-            ("100-continue",
-             {"100-continue":(None,)}),
-            ('foobar=twiddle',
-             {'foobar':('twiddle',)}),
-            ("foo=bar;a=b;c",
-             {'foo':('bar',('a', 'b'), ('c', None))})
-            )
-        self.runRoundtripTest("Expect", table)
-
-    def testFrom(self):
-        self.runRoundtripTest("From", (("webmaster at w3.org", "webmaster at w3.org"),))
-
-    def testHost(self):
-        self.runRoundtripTest("Host", (("www.w3.org", "www.w3.org"),))
-
-    def testIfMatch(self):
-        table = (
-            ('"xyzzy"', [http_headers.ETag('xyzzy')]),
-            ('"xyzzy", "r2d2xxxx", "c3piozzzz"', [http_headers.ETag('xyzzy'),
-                                                    http_headers.ETag('r2d2xxxx'),
-                                                    http_headers.ETag('c3piozzzz')]),
-            ('*', ['*']),
-            )
-    def testIfModifiedSince(self):
-        # Don't need major tests since the datetime parser has its own test
-        # Just test stupid ; length= brokenness.
-        table = (
-            ("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000),
-            ("Sun, 09 Sep 2001 01:46:40 GMT; length=500", 1000000000, ["Sun, 09 Sep 2001 01:46:40 GMT"]),
-            )
-
-        self.runRoundtripTest("If-Modified-Since", table)
-
-    def testIfNoneMatch(self):
-        table = (
-            ('"xyzzy"', [http_headers.ETag('xyzzy')]),
-            ('W/"xyzzy", "r2d2xxxx", "c3piozzzz"', [http_headers.ETag('xyzzy', weak=True),
-                                                    http_headers.ETag('r2d2xxxx'),
-                                                    http_headers.ETag('c3piozzzz')]),
-            ('W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"', [http_headers.ETag('xyzzy', weak=True),
-                                                        http_headers.ETag('r2d2xxxx', weak=True),
-                                                        http_headers.ETag('c3piozzzz', weak=True)]),
-            ('*', ['*']),
-            )
-        self.runRoundtripTest("If-None-Match", table)
-
-    def testIfRange(self):
-        table = (
-            ('"xyzzy"', http_headers.ETag('xyzzy')),
-            ('W/"xyzzy"', http_headers.ETag('xyzzy', weak=True)),
-            ('W/"xyzzy"', http_headers.ETag('xyzzy', weak=True)),
-            ("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000),
-            )
-        self.runRoundtripTest("If-Range", table)
-
-    def testIfUnmodifiedSince(self):
-        self.runRoundtripTest("If-Unmodified-Since", (("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000),))
-
-    def testMaxForwards(self):
-        self.runRoundtripTest("Max-Forwards", (("15", 15),))
-
-
-#     def testProxyAuthorize(self):
-#         fail
-
-    def testRange(self):
-        table = (
-            ("bytes=0-499", ('bytes', [(0,499),])),
-            ("bytes=500-999", ('bytes', [(500,999),])),
-            ("bytes=-500",('bytes', [(None,500),])),
-            ("bytes=9500-",('bytes', [(9500, None),])),
-            ("bytes=0-0,-1", ('bytes', [(0,0),(None,1)])),
-            )
-        self.runRoundtripTest("Range", table)
-
-
-    def testReferer(self):
-        self.runRoundtripTest("Referer", (("http://www.w3.org/hypertext/DataSources/Overview.html",
-                                           "http://www.w3.org/hypertext/DataSources/Overview.html"),))
-
-
-    def testTE(self):
-        table = (
-            ("deflate", {'deflate':1}),
-            ("", {}),
-            ("trailers, deflate;q=0.5", {'trailers':1, 'deflate':0.5}),
-            )
-        self.runRoundtripTest("TE", table)
-
-    def testUserAgent(self):
-        self.runRoundtripTest("User-Agent", (("CERN-LineMode/2.15 libwww/2.17b3", "CERN-LineMode/2.15 libwww/2.17b3"),))
-
-
-class ResponseHeaderParsingTests(HeaderParsingTestBase):
-    def testAcceptRanges(self):
-        self.runRoundtripTest("Accept-Ranges", (("bytes", ["bytes"]), ("none", ["none"])))
-
-    def testAge(self):
-        self.runRoundtripTest("Age", (("15", 15),))
-
-    def testETag(self):
-        table = (
-            ('"xyzzy"', http_headers.ETag('xyzzy')),
-            ('W/"xyzzy"', http_headers.ETag('xyzzy', weak=True)),
-            ('""', http_headers.ETag('')),
-            )
-        self.runRoundtripTest("ETag", table)
-
-    def testLocation(self):
-        self.runRoundtripTest("Location", (("http://www.w3.org/pub/WWW/People.htm",
-                                           "http://www.w3.org/pub/WWW/People.htm"),))
-
-
-#     def testProxyAuthenticate(self):
-#         fail
-
-    def testRetryAfter(self):
-        # time() is always 999999990 when being tested.
-        table = (
-            ("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000, ["10"]),
-            ("120", 999999990+120),
-            )
-        self.runRoundtripTest("Retry-After", table)
-
-    def testServer(self):
-        self.runRoundtripTest("Server", (("CERN/3.0 libwww/2.17", "CERN/3.0 libwww/2.17"),))
-
-    def testVary(self):
-        table = (
-            ("*", ["*"]),
-            ("Accept, Accept-Encoding", ["accept", "accept-encoding"], ["accept", "accept-encoding"])
-            )
-        self.runRoundtripTest("Vary", table)
-
-    def testWWWAuthenticate(self):
-        digest = ('Digest realm="digest realm", nonce="bAr", qop="auth"',
-                  [('Digest', {'realm': 'digest realm', 'nonce': 'bAr', 
-                               'qop': 'auth'})],
-                  ['Digest', 'realm="digest realm"', 
-                   'nonce="bAr"', 'qop="auth"'])
-
-        basic = ('Basic realm="foo"',
-                 [('Basic', {'realm': 'foo'})], ['Basic', 'realm="foo"'])
-
-        ntlm = ('NTLM',
-                [('NTLM', {})], ['NTLM', ''])
-
-        negotiate = ('Negotiate SomeGssAPIData',
-                     [('Negotiate', 'SomeGssAPIData')], 
-                     ['Negotiate', 'SomeGssAPIData'])
-
-        table = (digest,
-                 basic,
-                 (digest[0]+', '+basic[0],
-                  digest[1] + basic[1],
-                  [digest[2], basic[2]]),
-                 ntlm,
-                 negotiate,
-                 (ntlm[0]+', '+basic[0],
-                  ntlm[1] + basic[1],
-                  [ntlm[2], basic[2]]),
-                 (digest[0]+', '+negotiate[0],
-                  digest[1] + negotiate[1],
-                  [digest[2], negotiate[2]]),
-                 (negotiate[0]+', '+negotiate[0],
-                  negotiate[1] + negotiate[1],
-                  [negotiate[2] + negotiate[2]]),
-                 (ntlm[0]+', '+ntlm[0],
-                  ntlm[1] + ntlm[1],
-                  [ntlm[2], ntlm[2]]),
-                 (basic[0]+', '+ntlm[0],
-                  basic[1] + ntlm[1],
-                  [basic[2], ntlm[2]]),
-                 )
-
-        # runRoundtripTest doesn't work because we don't generate a single
-        # header
-
-        headername = 'WWW-Authenticate'
-
-        for row in table:
-            rawHeaderInput, parsedHeaderData, requiredGeneratedElements = row
-
-            parsed = parseHeader(headername, [rawHeaderInput,])
-            self.assertEquals(parsed, parsedHeaderData)
-
-            regeneratedHeaderValue = generateHeader(headername, parsed)
-
-            for regeneratedElement in regeneratedHeaderValue:
-                requiredElements = requiredGeneratedElements[
-                    regeneratedHeaderValue.index(
-                        regeneratedElement)]
-
-                for reqEle in requiredElements:
-                    elementIndex = regeneratedElement.find(reqEle)
-
-                    self.assertNotEqual(
-                        elementIndex, -1,
-                        "%r did not appear in generated HTTP header %r: %r" % (reqEle,
-                                                                               headername,
-                                                                               regeneratedElement))
-
-        # parser/generator
-        reparsed = parseHeader(headername, regeneratedHeaderValue)
-        self.assertEquals(parsed, reparsed)
-
-
-class EntityHeaderParsingTests(HeaderParsingTestBase):
-    def testAllow(self):
-        # Allow is a silly case-sensitive header unlike all the rest
-        table = (
-            ("GET", ['GET', ]),
-            ("GET, HEAD, PUT", ['GET', 'HEAD', 'PUT']),
-            )
-        self.runRoundtripTest("Allow", table)
-
-    def testContentEncoding(self):
-        table = (
-            ("gzip", ['gzip',]),
-            )
-        self.runRoundtripTest("Content-Encoding", table)
-
-    def testContentLanguage(self):
-        table = (
-            ("da", ['da',]),
-            ("mi, en", ['mi', 'en']),
-            )
-        self.runRoundtripTest("Content-Language", table)
-
-    def testContentLength(self):
-        self.runRoundtripTest("Content-Length", (("15", 15),))
-        self.invalidParseTest("Content-Length", ("asdf",))
-
-    def testContentLocation(self):
-        self.runRoundtripTest("Content-Location",
-                              (("http://www.w3.org/pub/WWW/People.htm",
-                                "http://www.w3.org/pub/WWW/People.htm"),))
-
-    def testContentMD5(self):
-        self.runRoundtripTest("Content-MD5", (("Q2hlY2sgSW50ZWdyaXR5IQ==", "Check Integrity!"),))
-        self.invalidParseTest("Content-MD5", ("sdlaksjdfhlkaj",))
-
-    def testContentRange(self):
-        table = (
-            ("bytes 0-499/1234", ("bytes", 0, 499, 1234)),
-            ("bytes 500-999/1234", ("bytes", 500, 999, 1234)),
-            ("bytes 500-1233/1234", ("bytes", 500, 1233, 1234)),
-            ("bytes 734-1233/1234", ("bytes", 734, 1233, 1234)),
-            ("bytes 734-1233/*", ("bytes", 734, 1233, None)),
-            ("bytes */1234", ("bytes", None, None, 1234)),
-            ("bytes */*", ("bytes", None, None, None))
-            )
-        self.runRoundtripTest("Content-Range", table)
-
-    def testContentType(self):
-        table = (
-            ("text/html;charset=iso-8859-4", http_headers.MimeType('text', 'html', (('charset','iso-8859-4'),))),
-            ("text/html", http_headers.MimeType('text', 'html')),
-            )
-        self.runRoundtripTest("Content-Type", table)
-
-    def testExpires(self):
-        self.runRoundtripTest("Expires", (("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000),))
-        # Invalid expires MUST return date in the past.
-        self.assertEquals(parseHeader("Expires", ["0"]), 0)
-        self.assertEquals(parseHeader("Expires", ["wejthnaljn"]), 0)
-
-
-    def testLastModified(self):
-        # Don't need major tests since the datetime parser has its own test
-        self.runRoundtripTest("Last-Modified", (("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000),))
-
-class DateTimeTest(unittest.TestCase):
-    """Test date parsing functions."""
-
-    def testParse(self):
-        timeNum = 784111777
-        timeStrs = ('Sun, 06 Nov 1994 08:49:37 GMT',
-                    'Sunday, 06-Nov-94 08:49:37 GMT',
-                    'Sun Nov  6 08:49:37 1994',
-
-                    # Also some non-RFC formats, for good measure.
-                    'Somefakeday 6 Nov 1994 8:49:37',
-                    '6 Nov 1994 8:49:37',
-                    'Sun, 6 Nov 1994 8:49:37',
-                    '6 Nov 1994 8:49:37 GMT',
-
-                    '06-Nov-94 08:49:37',
-                    'Sunday, 06-Nov-94 08:49:37',
-                    '06-Nov-94 08:49:37 GMT',
-
-                    'Nov  6 08:49:37 1994',
-                    )
-        for timeStr in timeStrs:
-            self.assertEquals(http_headers.parseDateTime(timeStr), timeNum)
-
-        # Test 2 Digit date wraparound yuckiness.
-        self.assertEquals(http_headers.parseDateTime(
-            'Monday, 11-Oct-04 14:56:50 GMT'), 1097506610)
-        self.assertEquals(http_headers.parseDateTime(
-            'Monday, 11-Oct-2004 14:56:50 GMT'), 1097506610)
-
-
-    def testGenerate(self):
-        self.assertEquals(http_headers.generateDateTime(784111777), 'Sun, 06 Nov 1994 08:49:37 GMT')
-
-    def testRoundtrip(self):
-        for i in range(2000):
-            time = random.randint(0, 2000000000)
-            timestr = http_headers.generateDateTime(time)
-            time2 = http_headers.parseDateTime(timestr)
-            self.assertEquals(time, time2)
-
-
-class TestMimeType(unittest.TestCase):
-    def testEquality(self):
-        """Test that various uses of the constructer are equal
-        """
-
-        kwargMime = http_headers.MimeType('text', 'plain',
-                                          key='value',
-                                          param=None)
-        dictMime = http_headers.MimeType('text', 'plain',
-                                         {'param': None,
-                                          'key': 'value'})
-        tupleMime = http_headers.MimeType('text', 'plain',
-                                          (('param', None),
-                                           ('key', 'value')))
-
-        stringMime = http_headers.MimeType.fromString('text/plain;key=value;param')
-
-        self.assertEquals(kwargMime, dictMime)
-        self.assertEquals(dictMime, tupleMime)
-        self.assertEquals(kwargMime, tupleMime)
-        self.assertEquals(kwargMime, stringMime)
-
-
-
-class FormattingUtilityTests(unittest.TestCase):
-    """
-    Tests for various string formatting functionality required to generate
-    headers.
-    """
-    def test_quoteString(self):
-        """
-        L{quoteString} returns a string which when interpreted according to the
-        rules for I{quoted-string} (RFC 2616 section 2.2) matches the input
-        string.
-        """
-        self.assertEqual(
-            quoteString('a\\b"c'),
-            '"a\\\\b\\"c"')
-
-
-    def test_generateKeyValues(self):
-        """
-        L{generateKeyValues} accepts an iterable of parameters and returns a
-        string formatted according to RFC 2045 section 5.1.
-        """
-        self.assertEqual(
-            generateKeyValues(iter([("foo", "bar"), ("baz", "quux")])),
-            "foo=bar;baz=quux")
-
-
-    def test_generateKeyValuesNone(self):
-        """
-        L{generateKeyValues} accepts C{None} as the 2nd element of a tuple and
-        includes just the 1st element in the output without an C{"="}.
-        """
-        self.assertEqual(
-            generateKeyValues([("foo", None), ("bar", "baz")]),
-            "foo;bar=baz")
-
-
-    def test_generateKeyValuesQuoting(self):
-        """
-        L{generateKeyValues} quotes the value of the 2nd element of a tuple if
-        it includes a character which cannot be in an HTTP token as defined in
-        RFC 2616 section 2.2.
-        """
-        for needsQuote in [' ', '\t', '(', ')', '<', '>', '@', ',', ';', ':',
-                           '\\', '"', '/', '[', ']', '?', '=', '{', '}']:
-            self.assertEqual(
-                generateKeyValues([("foo", needsQuote)]),
-                'foo=%s' % (quoteString(needsQuote),))

Copied: CalendarServer/trunk/twext/web2/test/test_http_headers.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_http_headers.py)
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_http_headers.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/test/test_http_headers.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,796 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twext.web2.http_headers}.
+"""
+
+from twisted.trial import unittest
+import random, time
+
+from twext.web2 import http_headers
+from twext.web2.http_headers import Cookie, HeaderHandler, quoteString, generateKeyValues
+from twisted.python import util
+
+class parsedvalue:
+    """Marker class"""
+    def __init__(self, raw):
+        self.raw = raw
+
+    def __eq__(self, other):
+        return isinstance(other, parsedvalue) and other.raw == self.raw
+
+class HeadersAPITest(unittest.TestCase):
+    """Make sure the public API exists and works."""
+    def testRaw(self):
+        rawvalue = ("value1", "value2")
+        h = http_headers.Headers(handler=HeaderHandler(parsers={}, generators={}))
+        h.setRawHeaders("test", rawvalue)
+        self.assertEquals(h.hasHeader("test"), True)
+        self.assertEquals(h.getRawHeaders("test"), rawvalue)
+        self.assertEquals(list(h.getAllRawHeaders()), [('Test', rawvalue)])
+
+        self.assertEquals(h.getRawHeaders("foobar"), None)
+        h.removeHeader("test")
+        self.assertEquals(h.getRawHeaders("test"), None)
+
+    def testParsed(self):
+        parsed = parsedvalue(("value1", "value2"))
+        h = http_headers.Headers(handler=HeaderHandler(parsers={}, generators={}))
+
+        h.setHeader("test", parsed)
+        self.assertEquals(h.hasHeader("test"), True)
+        self.assertEquals(h.getHeader("test"), parsed)
+
+        self.assertEquals(h.getHeader("foobar"), None)
+        h.removeHeader("test")
+        self.assertEquals(h.getHeader("test"), None)
+
+    def testParsedAndRaw(self):
+        def parse(raw):
+            return parsedvalue(raw)
+
+        def generate(parsed):
+            return parsed.raw
+
+        rawvalue = ("value1", "value2")
+        rawvalue2 = ("value3", "value4")
+        handler = HeaderHandler(parsers={'test':(parse,)},
+                                generators={'test':(generate,)})
+
+        h = http_headers.Headers(handler=handler)
+        h.setRawHeaders("test", rawvalue)
+        self.assertEquals(h.getHeader("test"), parsedvalue(rawvalue))
+
+        h.setHeader("test", parsedvalue(rawvalue2))
+        self.assertEquals(h.getRawHeaders("test"), rawvalue2)
+
+        # Check the initializers
+        h = http_headers.Headers(rawHeaders={"test": rawvalue},
+                                 handler=handler)
+        self.assertEquals(h.getHeader("test"), parsedvalue(rawvalue))
+
+        h = http_headers.Headers({"test": parsedvalue(rawvalue2)},
+                                 handler=handler)
+        self.assertEquals(h.getRawHeaders("test"), rawvalue2)
+
+    def testImmutable(self):
+        h = http_headers.Headers(handler=HeaderHandler(parsers={}, generators={}))
+
+        h.makeImmutable()
+        self.assertRaises(AttributeError, h.setRawHeaders, "test", [1])
+        self.assertRaises(AttributeError, h.setHeader, "test", 1)
+        self.assertRaises(AttributeError, h.removeHeader, "test")
+
+class TokenizerTest(unittest.TestCase):
+    """Test header list parsing functions."""
+
+    def testParse(self):
+        parser = lambda val: list(http_headers.tokenize([val,]))
+        Token = http_headers.Token
+        tests = (('foo,bar', ['foo', Token(','), 'bar']),
+                 ('FOO,BAR', ['foo', Token(','), 'bar']),
+                 (' \t foo  \t bar  \t  ,  \t baz   ', ['foo', Token(' '), 'bar', Token(','), 'baz']),
+                 ('()<>@,;:\\/[]?={}', [Token('('), Token(')'), Token('<'), Token('>'), Token('@'), Token(','), Token(';'), Token(':'), Token('\\'), Token('/'), Token('['), Token(']'), Token('?'), Token('='), Token('{'), Token('}')]),
+                 (' "foo" ', ['foo']),
+                 ('"FOO(),\\"BAR,"', ['FOO(),"BAR,']))
+
+        raiseTests = ('"open quote', '"ending \\', "control character: \x127", "\x00", "\x1f")
+
+        for test,result in tests:
+            self.assertEquals(parser(test), result)
+        for test in raiseTests:
+            self.assertRaises(ValueError, parser, test)
+
+    def testGenerate(self):
+        pass
+
+    def testRoundtrip(self):
+        pass
+
+def atSpecifiedTime(when, func):
+    def inner(*a, **kw):
+        orig = time.time
+        time.time = lambda: when
+        try:
+            return func(*a, **kw)
+        finally:
+            time.time = orig
+    return util.mergeFunctionMetadata(func, inner)
+
+def parseHeader(name, val):
+    head = http_headers.Headers(handler=http_headers.DefaultHTTPHandler)
+    head.setRawHeaders(name,val)
+    return head.getHeader(name)
+parseHeader = atSpecifiedTime(999999990, parseHeader) # Sun, 09 Sep 2001 01:46:30 GMT
+
+def generateHeader(name, val):
+    head = http_headers.Headers(handler=http_headers.DefaultHTTPHandler)
+    head.setHeader(name, val)
+    return head.getRawHeaders(name)
+generateHeader = atSpecifiedTime(999999990, generateHeader) # Sun, 09 Sep 2001 01:46:30 GMT
+
+
+class HeaderParsingTestBase(unittest.TestCase):
+    def runRoundtripTest(self, headername, table):
+        """
+        Perform some assertions about the behavior of parsing and
+        generating HTTP headers.  Specifically: parse an HTTP header
+        value, assert that the parsed form contains all the available
+        information with the correct structure; generate the HTTP
+        header value from the parsed form, assert that it contains
+        certain literal strings; finally, re-parse the generated HTTP
+        header value and assert that the resulting structured data is
+        the same as the first-pass parsed form.
+
+        @type headername: C{str}
+        @param headername: The name of the HTTP header L{table} contains values for.
+
+        @type table: A sequence of tuples describing inputs to and
+        outputs from header parsing and generation.  The tuples may be
+        either 2 or 3 elements long.  In either case: the first
+        element is a string representing an HTTP-format header value;
+        the second element is a dictionary mapping names of parameters
+        to values of those parameters (the parsed form of the header).
+        If there is a third element, it is a list of strings which
+        must occur exactly in the HTTP header value
+        string which is re-generated from the parsed form.
+        """
+        for row in table:
+            if len(row) == 2:
+                rawHeaderInput, parsedHeaderData = row
+                requiredGeneratedElements = []
+            elif len(row) == 3:
+                rawHeaderInput, parsedHeaderData, requiredGeneratedElements = row
+
+
+            assert isinstance(requiredGeneratedElements, list)
+
+            # parser
+            parsed = parseHeader(headername, [rawHeaderInput,])
+            self.assertEquals(parsed, parsedHeaderData)
+
+            regeneratedHeaderValue = generateHeader(headername, parsed)
+
+            if requiredGeneratedElements:
+                # generator
+                for regeneratedElement in regeneratedHeaderValue:
+                    reqEle = requiredGeneratedElements[regeneratedHeaderValue.index(regeneratedElement)]
+                    elementIndex = regeneratedElement.find(reqEle)
+                    self.assertNotEqual(
+                        elementIndex, -1,
+                        "%r did not appear in generated HTTP header %r: %r" % (reqEle,
+                                                                               headername,
+                                                                               regeneratedElement))
+
+            # parser/generator
+            reparsed = parseHeader(headername, regeneratedHeaderValue)
+            self.assertEquals(parsed, reparsed)
+
+
+    def invalidParseTest(self, headername, values):
+        for val in values:
+            parsed = parseHeader(headername, val)
+            self.assertEquals(parsed, None)
+
+class GeneralHeaderParsingTests(HeaderParsingTestBase):
+    def testCacheControl(self):
+        table = (
+            ("no-cache",
+             {'no-cache':None}),
+            ("no-cache, no-store, max-age=5, max-stale=3, min-fresh=5, no-transform, only-if-cached, blahblah-extension-thingy",
+             {'no-cache': None,
+              'no-store': None,
+              'max-age':5,
+              'max-stale':3,
+              'min-fresh':5,
+              'no-transform':None,
+              'only-if-cached':None,
+              'blahblah-extension-thingy':None}),
+            ("max-stale",
+             {'max-stale':None}),
+            ("public, private, no-cache, no-store, no-transform, must-revalidate, proxy-revalidate, max-age=5, s-maxage=10, blahblah-extension-thingy",
+             {'public':None,
+              'private':None,
+              'no-cache':None,
+              'no-store':None,
+              'no-transform':None,
+              'must-revalidate':None,
+              'proxy-revalidate':None,
+              'max-age':5,
+              's-maxage':10,
+              'blahblah-extension-thingy':None}),
+            ('private="Set-Cookie, Set-Cookie2", no-cache="PROXY-AUTHENTICATE"',
+             {'private': ['set-cookie', 'set-cookie2'],
+              'no-cache': ['proxy-authenticate']},
+             ['private="Set-Cookie, Set-Cookie2"', 'no-cache="Proxy-Authenticate"']),
+            )
+        self.runRoundtripTest("Cache-Control", table)
+
+    def testConnection(self):
+        table = (
+            ("close", ['close',]),
+            ("close, foo-bar", ['close', 'foo-bar'])
+            )
+        self.runRoundtripTest("Connection", table)
+
+    def testDate(self):
+        # Don't need major tests since the datetime parser has its own tests
+        self.runRoundtripTest("Date", (("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000),))
+
+#     def testPragma(self):
+#         fail
+
+#     def testTrailer(self):
+#         fail
+
+    def testTransferEncoding(self):
+        table = (
+            ('chunked', ['chunked']),
+            ('gzip, chunked', ['gzip', 'chunked'])
+            )
+        self.runRoundtripTest("Transfer-Encoding", table)
+
+#     def testUpgrade(self):
+#         fail
+
+#     def testVia(self):
+#         fail
+
+#     def testWarning(self):
+#         fail
+
+class RequestHeaderParsingTests(HeaderParsingTestBase):
+    #FIXME test ordering too.
+    def testAccept(self):
+        table = (
+            ("audio/*;q=0.2, audio/basic",
+             {http_headers.MimeType('audio', '*'): 0.2,
+              http_headers.MimeType('audio', 'basic'): 1.0}),
+
+            ("text/plain;q=0.5, text/html, text/x-dvi;q=0.8, text/x-c",
+             {http_headers.MimeType('text', 'plain'): 0.5,
+              http_headers.MimeType('text', 'html'): 1.0,
+              http_headers.MimeType('text', 'x-dvi'): 0.8,
+              http_headers.MimeType('text', 'x-c'): 1.0}),
+
+            ("text/*, text/html, text/html;level=1, */*",
+             {http_headers.MimeType('text', '*'): 1.0,
+              http_headers.MimeType('text', 'html'): 1.0,
+              http_headers.MimeType('text', 'html', (('level', '1'),)): 1.0,
+              http_headers.MimeType('*', '*'): 1.0}),
+
+       ("text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5",
+             {http_headers.MimeType('text', '*'): 0.3,
+              http_headers.MimeType('text', 'html'): 0.7,
+              http_headers.MimeType('text', 'html', (('level', '1'),)): 1.0,
+              http_headers.MimeType('text', 'html', (('level', '2'),)): 0.4,
+              http_headers.MimeType('*', '*'): 0.5}),
+            )
+
+        self.runRoundtripTest("Accept", table)
+
+
+    def testAcceptCharset(self):
+        table = (
+            ("iso-8859-5, unicode-1-1;q=0.8",
+             {'iso-8859-5': 1.0, 'iso-8859-1': 1.0, 'unicode-1-1': 0.8},
+             ["iso-8859-5", "unicode-1-1;q=0.8", "iso-8859-1"]),
+            ("iso-8859-1;q=0.7",
+             {'iso-8859-1': 0.7}),
+            ("*;q=.7",
+             {'*': 0.7},
+             ["*;q=0.7"]),
+            ("",
+             {'iso-8859-1': 1.0},
+             ["iso-8859-1"]), # Yes this is an actual change -- we'll say that's okay. :)
+            )
+        self.runRoundtripTest("Accept-Charset", table)
+
+    def testAcceptEncoding(self):
+        table = (
+            ("compress, gzip",
+             {'compress': 1.0, 'gzip': 1.0, 'identity': 0.0001}),
+            ("",
+             {'identity': 0.0001}),
+            ("*",
+             {'*': 1}),
+            ("compress;q=0.5, gzip;q=1.0",
+             {'compress': 0.5, 'gzip': 1.0, 'identity': 0.0001},
+             ["compress;q=0.5", "gzip"]),
+            ("gzip;q=1.0, identity;q=0.5, *;q=0",
+             {'gzip': 1.0, 'identity': 0.5, '*':0},
+             ["gzip", "identity;q=0.5", "*;q=0"]),
+            )
+        self.runRoundtripTest("Accept-Encoding", table)
+
+    def testAcceptLanguage(self):
+        table = (
+            ("da, en-gb;q=0.8, en;q=0.7",
+             {'da': 1.0, 'en-gb': 0.8, 'en': 0.7}),
+            ("*",
+             {'*': 1}),
+            )
+        self.runRoundtripTest("Accept-Language", table)
+
+    def testAuthorization(self):
+        table = (
+            ("Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
+             ("basic", "dXNlcm5hbWU6cGFzc3dvcmQ="),
+             ["basic dXNlcm5hbWU6cGFzc3dvcmQ="]),
+            ('Digest nonce="bar", realm="foo", username="baz", response="bax"',
+             ('digest', 'nonce="bar", realm="foo", username="baz", response="bax"'),
+             ['digest', 'nonce="bar"', 'realm="foo"', 'username="baz"', 'response="bax"'])
+            )
+
+        self.runRoundtripTest("Authorization", table)
+
+    def testCookie(self):
+        table = (
+            ('name=value', [Cookie('name', 'value')]),
+            ('"name"="value"', [Cookie('"name"', '"value"')]),
+            ('name,"blah=value,"', [Cookie('name,"blah', 'value,"')]),
+            ('name,"blah  = value,"  ', [Cookie('name,"blah', 'value,"')], ['name,"blah=value,"']),
+            ("`~!@#$%^&*()-_+[{]}\\|:'\",<.>/?=`~!@#$%^&*()-_+[{]}\\|:'\",<.>/?", [Cookie("`~!@#$%^&*()-_+[{]}\\|:'\",<.>/?", "`~!@#$%^&*()-_+[{]}\\|:'\",<.>/?")]),
+            ('name,"blah  = value,"  ; name2=val2',
+               [Cookie('name,"blah', 'value,"'), Cookie('name2', 'val2')],
+               ['name,"blah=value,"', 'name2=val2']),
+            )
+        self.runRoundtripTest("Cookie", table)
+
+        #newstyle RFC2965 Cookie
+        table2 = (
+            ('$Version="1";'
+             'name="value";$Path="/foo";$Domain="www.local";$Port="80,8000";'
+             'name2="value"',
+             [Cookie('name', 'value', path='/foo', domain='www.local', ports=(80,8000), version=1), Cookie('name2', 'value', version=1)]),
+            ('$Version="1";'
+             'name="value";$Port',
+             [Cookie('name', 'value', ports=(), version=1)]),
+            ('$Version = 1, NAME = "qq\\"qq",Frob=boo',
+             [Cookie('name', 'qq"qq', version=1), Cookie('frob', 'boo', version=1)],
+             ['$Version="1";name="qq\\"qq";frob="boo"']),
+            )
+        self.runRoundtripTest("Cookie", table2)
+
+        # Generate only!
+        # make headers by combining oldstyle and newstyle cookies
+        table3 = (
+            ([Cookie('name', 'value'), Cookie('name2', 'value2', version=1)],
+             '$Version="1";name=value;name2="value2"'),
+            ([Cookie('name', 'value', path="/foo"), Cookie('name2', 'value2', domain="bar.baz", version=1)],
+             '$Version="1";name=value;$Path="/foo";name2="value2";$Domain="bar.baz"'),
+            ([Cookie('invalid,"name', 'value'), Cookie('name2', 'value2', version=1)],
+             '$Version="1";name2="value2"'),
+            ([Cookie('name', 'qq"qq'), Cookie('name2', 'value2', version=1)],
+             '$Version="1";name="qq\\"qq";name2="value2"'),
+            )
+        for row in table3:
+            self.assertEquals(generateHeader("Cookie", row[0]), [row[1],])
+
+
+
+    def testSetCookie(self):
+        table = (
+            ('name,"blah=value,; expires=Sun, 09 Sep 2001 01:46:40 GMT; path=/foo; domain=bar.baz; secure',
+             [Cookie('name,"blah', 'value,', expires=1000000000, path="/foo", domain="bar.baz", secure=True)]),
+            ('name,"blah = value, ; expires="Sun, 09 Sep 2001 01:46:40 GMT"',
+             [Cookie('name,"blah', 'value,', expires=1000000000)],
+             ['name,"blah=value,', 'expires=Sun, 09 Sep 2001 01:46:40 GMT']),
+            )
+        self.runRoundtripTest("Set-Cookie", table)
+
+    def testSetCookie2(self):
+        table = (
+            ('name="value"; Comment="YadaYada"; CommentURL="http://frobnotz/"; Discard; Domain="blah.blah"; Max-Age=10; Path="/foo"; Port="80,8080"; Secure; Version="1"',
+             [Cookie("name", "value", comment="YadaYada", commenturl="http://frobnotz/", discard=True, domain="blah.blah", expires=1000000000, path="/foo", ports=(80,8080), secure=True, version=1)]),
+            )
+        self.runRoundtripTest("Set-Cookie2", table)
+
+    def testExpect(self):
+        table = (
+            ("100-continue",
+             {"100-continue":(None,)}),
+            ('foobar=twiddle',
+             {'foobar':('twiddle',)}),
+            ("foo=bar;a=b;c",
+             {'foo':('bar',('a', 'b'), ('c', None))})
+            )
+        self.runRoundtripTest("Expect", table)
+
+    def testFrom(self):
+        self.runRoundtripTest("From", (("webmaster at w3.org", "webmaster at w3.org"),))
+
+    def testHost(self):
+        self.runRoundtripTest("Host", (("www.w3.org", "www.w3.org"),))
+
+    def testIfMatch(self):
+        table = (
+            ('"xyzzy"', [http_headers.ETag('xyzzy')]),
+            ('"xyzzy", "r2d2xxxx", "c3piozzzz"', [http_headers.ETag('xyzzy'),
+                                                    http_headers.ETag('r2d2xxxx'),
+                                                    http_headers.ETag('c3piozzzz')]),
+            ('*', ['*']),
+            )
+    def testIfModifiedSince(self):
+        # Don't need major tests since the datetime parser has its own test
+        # Just test stupid ; length= brokenness.
+        table = (
+            ("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000),
+            ("Sun, 09 Sep 2001 01:46:40 GMT; length=500", 1000000000, ["Sun, 09 Sep 2001 01:46:40 GMT"]),
+            )
+
+        self.runRoundtripTest("If-Modified-Since", table)
+
+    def testIfNoneMatch(self):
+        table = (
+            ('"xyzzy"', [http_headers.ETag('xyzzy')]),
+            ('W/"xyzzy", "r2d2xxxx", "c3piozzzz"', [http_headers.ETag('xyzzy', weak=True),
+                                                    http_headers.ETag('r2d2xxxx'),
+                                                    http_headers.ETag('c3piozzzz')]),
+            ('W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"', [http_headers.ETag('xyzzy', weak=True),
+                                                        http_headers.ETag('r2d2xxxx', weak=True),
+                                                        http_headers.ETag('c3piozzzz', weak=True)]),
+            ('*', ['*']),
+            )
+        self.runRoundtripTest("If-None-Match", table)
+
+    def testIfRange(self):
+        table = (
+            ('"xyzzy"', http_headers.ETag('xyzzy')),
+            ('W/"xyzzy"', http_headers.ETag('xyzzy', weak=True)),
+            ('W/"xyzzy"', http_headers.ETag('xyzzy', weak=True)),
+            ("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000),
+            )
+        self.runRoundtripTest("If-Range", table)
+
+    def testIfUnmodifiedSince(self):
+        self.runRoundtripTest("If-Unmodified-Since", (("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000),))
+
+    def testMaxForwards(self):
+        self.runRoundtripTest("Max-Forwards", (("15", 15),))
+
+
+#     def testProxyAuthorize(self):
+#         fail
+
+    def testRange(self):
+        table = (
+            ("bytes=0-499", ('bytes', [(0,499),])),
+            ("bytes=500-999", ('bytes', [(500,999),])),
+            ("bytes=-500",('bytes', [(None,500),])),
+            ("bytes=9500-",('bytes', [(9500, None),])),
+            ("bytes=0-0,-1", ('bytes', [(0,0),(None,1)])),
+            )
+        self.runRoundtripTest("Range", table)
+
+
+    def testReferer(self):
+        self.runRoundtripTest("Referer", (("http://www.w3.org/hypertext/DataSources/Overview.html",
+                                           "http://www.w3.org/hypertext/DataSources/Overview.html"),))
+
+
+    def testTE(self):
+        table = (
+            ("deflate", {'deflate':1}),
+            ("", {}),
+            ("trailers, deflate;q=0.5", {'trailers':1, 'deflate':0.5}),
+            )
+        self.runRoundtripTest("TE", table)
+
+    def testUserAgent(self):
+        self.runRoundtripTest("User-Agent", (("CERN-LineMode/2.15 libwww/2.17b3", "CERN-LineMode/2.15 libwww/2.17b3"),))
+
+
+class ResponseHeaderParsingTests(HeaderParsingTestBase):
+    def testAcceptRanges(self):
+        self.runRoundtripTest("Accept-Ranges", (("bytes", ["bytes"]), ("none", ["none"])))
+
+    def testAge(self):
+        self.runRoundtripTest("Age", (("15", 15),))
+
+    def testETag(self):
+        table = (
+            ('"xyzzy"', http_headers.ETag('xyzzy')),
+            ('W/"xyzzy"', http_headers.ETag('xyzzy', weak=True)),
+            ('""', http_headers.ETag('')),
+            )
+        self.runRoundtripTest("ETag", table)
+
+    def testLocation(self):
+        self.runRoundtripTest("Location", (("http://www.w3.org/pub/WWW/People.htm",
+                                           "http://www.w3.org/pub/WWW/People.htm"),))
+
+
+#     def testProxyAuthenticate(self):
+#         fail
+
+    def testRetryAfter(self):
+        # time() is always 999999990 when being tested.
+        table = (
+            ("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000, ["10"]),
+            ("120", 999999990+120),
+            )
+        self.runRoundtripTest("Retry-After", table)
+
+    def testServer(self):
+        self.runRoundtripTest("Server", (("CERN/3.0 libwww/2.17", "CERN/3.0 libwww/2.17"),))
+
+    def testVary(self):
+        table = (
+            ("*", ["*"]),
+            ("Accept, Accept-Encoding", ["accept", "accept-encoding"], ["accept", "accept-encoding"])
+            )
+        self.runRoundtripTest("Vary", table)
+
+    def testWWWAuthenticate(self):
+        digest = ('Digest realm="digest realm", nonce="bAr", qop="auth"',
+                  [('Digest', {'realm': 'digest realm', 'nonce': 'bAr', 
+                               'qop': 'auth'})],
+                  ['Digest', 'realm="digest realm"', 
+                   'nonce="bAr"', 'qop="auth"'])
+
+        basic = ('Basic realm="foo"',
+                 [('Basic', {'realm': 'foo'})], ['Basic', 'realm="foo"'])
+
+        ntlm = ('NTLM',
+                [('NTLM', {})], ['NTLM', ''])
+
+        negotiate = ('Negotiate SomeGssAPIData',
+                     [('Negotiate', 'SomeGssAPIData')], 
+                     ['Negotiate', 'SomeGssAPIData'])
+
+        table = (digest,
+                 basic,
+                 (digest[0]+', '+basic[0],
+                  digest[1] + basic[1],
+                  [digest[2], basic[2]]),
+                 ntlm,
+                 negotiate,
+                 (ntlm[0]+', '+basic[0],
+                  ntlm[1] + basic[1],
+                  [ntlm[2], basic[2]]),
+                 (digest[0]+', '+negotiate[0],
+                  digest[1] + negotiate[1],
+                  [digest[2], negotiate[2]]),
+                 (negotiate[0]+', '+negotiate[0],
+                  negotiate[1] + negotiate[1],
+                  [negotiate[2] + negotiate[2]]),
+                 (ntlm[0]+', '+ntlm[0],
+                  ntlm[1] + ntlm[1],
+                  [ntlm[2], ntlm[2]]),
+                 (basic[0]+', '+ntlm[0],
+                  basic[1] + ntlm[1],
+                  [basic[2], ntlm[2]]),
+                 )
+
+        # runRoundtripTest doesn't work because we don't generate a single
+        # header
+
+        headername = 'WWW-Authenticate'
+
+        for row in table:
+            rawHeaderInput, parsedHeaderData, requiredGeneratedElements = row
+
+            parsed = parseHeader(headername, [rawHeaderInput,])
+            self.assertEquals(parsed, parsedHeaderData)
+
+            regeneratedHeaderValue = generateHeader(headername, parsed)
+
+            for regeneratedElement in regeneratedHeaderValue:
+                requiredElements = requiredGeneratedElements[
+                    regeneratedHeaderValue.index(
+                        regeneratedElement)]
+
+                for reqEle in requiredElements:
+                    elementIndex = regeneratedElement.find(reqEle)
+
+                    self.assertNotEqual(
+                        elementIndex, -1,
+                        "%r did not appear in generated HTTP header %r: %r" % (reqEle,
+                                                                               headername,
+                                                                               regeneratedElement))
+
+        # parser/generator
+        reparsed = parseHeader(headername, regeneratedHeaderValue)
+        self.assertEquals(parsed, reparsed)
+
+
+class EntityHeaderParsingTests(HeaderParsingTestBase):
+    def testAllow(self):
+        # Allow is a silly case-sensitive header unlike all the rest
+        table = (
+            ("GET", ['GET', ]),
+            ("GET, HEAD, PUT", ['GET', 'HEAD', 'PUT']),
+            )
+        self.runRoundtripTest("Allow", table)
+
+    def testContentEncoding(self):
+        table = (
+            ("gzip", ['gzip',]),
+            )
+        self.runRoundtripTest("Content-Encoding", table)
+
+    def testContentLanguage(self):
+        table = (
+            ("da", ['da',]),
+            ("mi, en", ['mi', 'en']),
+            )
+        self.runRoundtripTest("Content-Language", table)
+
+    def testContentLength(self):
+        self.runRoundtripTest("Content-Length", (("15", 15),))
+        self.invalidParseTest("Content-Length", ("asdf",))
+
+    def testContentLocation(self):
+        self.runRoundtripTest("Content-Location",
+                              (("http://www.w3.org/pub/WWW/People.htm",
+                                "http://www.w3.org/pub/WWW/People.htm"),))
+
+    def testContentMD5(self):
+        self.runRoundtripTest("Content-MD5", (("Q2hlY2sgSW50ZWdyaXR5IQ==", "Check Integrity!"),))
+        self.invalidParseTest("Content-MD5", ("sdlaksjdfhlkaj",))
+
+    def testContentRange(self):
+        table = (
+            ("bytes 0-499/1234", ("bytes", 0, 499, 1234)),
+            ("bytes 500-999/1234", ("bytes", 500, 999, 1234)),
+            ("bytes 500-1233/1234", ("bytes", 500, 1233, 1234)),
+            ("bytes 734-1233/1234", ("bytes", 734, 1233, 1234)),
+            ("bytes 734-1233/*", ("bytes", 734, 1233, None)),
+            ("bytes */1234", ("bytes", None, None, 1234)),
+            ("bytes */*", ("bytes", None, None, None))
+            )
+        self.runRoundtripTest("Content-Range", table)
+
+    def testContentType(self):
+        table = (
+            ("text/html;charset=iso-8859-4", http_headers.MimeType('text', 'html', (('charset','iso-8859-4'),))),
+            ("text/html", http_headers.MimeType('text', 'html')),
+            )
+        self.runRoundtripTest("Content-Type", table)
+
+    def testExpires(self):
+        self.runRoundtripTest("Expires", (("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000),))
+        # Invalid expires MUST return date in the past.
+        self.assertEquals(parseHeader("Expires", ["0"]), 0)
+        self.assertEquals(parseHeader("Expires", ["wejthnaljn"]), 0)
+
+
+    def testLastModified(self):
+        # Don't need major tests since the datetime parser has its own test
+        self.runRoundtripTest("Last-Modified", (("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000),))
+
+class DateTimeTest(unittest.TestCase):
+    """Test date parsing functions."""
+
+    def testParse(self):
+        timeNum = 784111777
+        timeStrs = ('Sun, 06 Nov 1994 08:49:37 GMT',
+                    'Sunday, 06-Nov-94 08:49:37 GMT',
+                    'Sun Nov  6 08:49:37 1994',
+
+                    # Also some non-RFC formats, for good measure.
+                    'Somefakeday 6 Nov 1994 8:49:37',
+                    '6 Nov 1994 8:49:37',
+                    'Sun, 6 Nov 1994 8:49:37',
+                    '6 Nov 1994 8:49:37 GMT',
+
+                    '06-Nov-94 08:49:37',
+                    'Sunday, 06-Nov-94 08:49:37',
+                    '06-Nov-94 08:49:37 GMT',
+
+                    'Nov  6 08:49:37 1994',
+                    )
+        for timeStr in timeStrs:
+            self.assertEquals(http_headers.parseDateTime(timeStr), timeNum)
+
+        # Test 2 Digit date wraparound yuckiness.
+        self.assertEquals(http_headers.parseDateTime(
+            'Monday, 11-Oct-04 14:56:50 GMT'), 1097506610)
+        self.assertEquals(http_headers.parseDateTime(
+            'Monday, 11-Oct-2004 14:56:50 GMT'), 1097506610)
+
+
+    def testGenerate(self):
+        self.assertEquals(http_headers.generateDateTime(784111777), 'Sun, 06 Nov 1994 08:49:37 GMT')
+
+    def testRoundtrip(self):
+        for i in range(2000):
+            time = random.randint(0, 2000000000)
+            timestr = http_headers.generateDateTime(time)
+            time2 = http_headers.parseDateTime(timestr)
+            self.assertEquals(time, time2)
+
+
+class TestMimeType(unittest.TestCase):
+    def testEquality(self):
+        """Test that various uses of the constructer are equal
+        """
+
+        kwargMime = http_headers.MimeType('text', 'plain',
+                                          key='value',
+                                          param=None)
+        dictMime = http_headers.MimeType('text', 'plain',
+                                         {'param': None,
+                                          'key': 'value'})
+        tupleMime = http_headers.MimeType('text', 'plain',
+                                          (('param', None),
+                                           ('key', 'value')))
+
+        stringMime = http_headers.MimeType.fromString('text/plain;key=value;param')
+
+        self.assertEquals(kwargMime, dictMime)
+        self.assertEquals(dictMime, tupleMime)
+        self.assertEquals(kwargMime, tupleMime)
+        self.assertEquals(kwargMime, stringMime)
+
+
+
+class FormattingUtilityTests(unittest.TestCase):
+    """
+    Tests for various string formatting functionality required to generate
+    headers.
+    """
+    def test_quoteString(self):
+        """
+        L{quoteString} returns a string which when interpreted according to the
+        rules for I{quoted-string} (RFC 2616 section 2.2) matches the input
+        string.
+        """
+        self.assertEqual(
+            quoteString('a\\b"c'),
+            '"a\\\\b\\"c"')
+
+
+    def test_generateKeyValues(self):
+        """
+        L{generateKeyValues} accepts an iterable of parameters and returns a
+        string formatted according to RFC 2045 section 5.1.
+        """
+        self.assertEqual(
+            generateKeyValues(iter([("foo", "bar"), ("baz", "quux")])),
+            "foo=bar;baz=quux")
+
+
+    def test_generateKeyValuesNone(self):
+        """
+        L{generateKeyValues} accepts C{None} as the 2nd element of a tuple and
+        includes just the 1st element in the output without an C{"="}.
+        """
+        self.assertEqual(
+            generateKeyValues([("foo", None), ("bar", "baz")]),
+            "foo;bar=baz")
+
+
+    def test_generateKeyValuesQuoting(self):
+        """
+        L{generateKeyValues} quotes the value of the 2nd element of a tuple if
+        it includes a character which cannot be in an HTTP token as defined in
+        RFC 2616 section 2.2.
+        """
+        for needsQuote in [' ', '\t', '(', ')', '<', '>', '@', ',', ';', ':',
+                           '\\', '"', '/', '[', ']', '?', '=', '{', '}']:
+            self.assertEqual(
+                generateKeyValues([("foo", needsQuote)]),
+                'foo=%s' % (quoteString(needsQuote),))

Deleted: CalendarServer/trunk/twext/web2/test/test_httpauth.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_httpauth.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/test/test_httpauth.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,968 +0,0 @@
-# Copyright (c) 2006-2009 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-from twisted.python.hashlib import md5
-from twisted.internet import address
-from twisted.trial import unittest
-from twisted.cred import error
-from twext.web2 import http, responsecode
-from twext.web2.auth import basic, digest, wrapper
-from twext.web2.auth.interfaces import IAuthenticatedRequest, IHTTPUser
-from twext.web2.test.test_server import SimpleRequest
-
-from twext.web2.test import test_server
-
-import base64
-
-_trivial_GET = SimpleRequest(None, 'GET', '/')
-
-class FakeDigestCredentialFactory(digest.DigestCredentialFactory):
-    """
-    A Fake Digest Credential Factory that generates a predictable
-    nonce and opaque
-    """
-
-    def __init__(self, *args, **kwargs):
-        super(FakeDigestCredentialFactory, self).__init__(*args, **kwargs)
-
-        self.privateKey = "0"
-
-    def generateNonce(self):
-        """
-        Generate a static nonce
-        """
-        return '178288758716122392881254770685'
-
-    def _getTime(self):
-        """
-        Return a stable time
-        """
-        return 0
-
-
-class BasicAuthTestCase(unittest.TestCase):
-    def setUp(self):
-        self.credentialFactory = basic.BasicCredentialFactory('foo')
-        self.username = 'dreid'
-        self.password = 'S3CuR1Ty'
-
-
-    def test_usernamePassword(self):
-        """
-        Test acceptance of username/password in basic auth.
-        """
-        response = base64.encodestring('%s:%s' % (
-                self.username,
-                self.password))
-
-        d = self.credentialFactory.decode(response, _trivial_GET)
-        return d.addCallback(
-            lambda creds: self.failUnless(creds.checkPassword(self.password)))
-
-
-    def test_incorrectPassword(self):
-        """
-        Incorrect passwords cause auth to fail.
-        """
-        response = base64.encodestring('%s:%s' % (
-                self.username,
-                'incorrectPassword'))
-
-        d = self.credentialFactory.decode(response, _trivial_GET)
-        return d.addCallback(
-            lambda creds: self.failIf(creds.checkPassword(self.password)))
-
-
-    def test_incorrectPadding(self):
-        """
-        Responses that have incorrect padding cause auth to fail.
-        """
-        response = base64.encodestring('%s:%s' % (
-                self.username,
-                self.password))
-
-        response = response.strip('=')
-
-        d = self.credentialFactory.decode(response, _trivial_GET)
-        def _test(creds):
-            self.failUnless(creds.checkPassword(self.password))
-        return d.addCallback(_test)
-
-
-    def test_invalidCredentials(self):
-        """
-        Auth attempts with no password should fail.
-        """
-        response = base64.encodestring(self.username)
-        d = self.credentialFactory.decode(response, _trivial_GET)
-        self.assertFailure(d, error.LoginFailed)
-
-
-clientAddress = address.IPv4Address('TCP', '127.0.0.1', 80)
-
-challengeOpaque = ('75c4bd95b96b7b7341c646c6502f0833-MTc4Mjg4NzU'
-                   '4NzE2MTIyMzkyODgxMjU0NzcwNjg1LHJlbW90ZWhvc3Q'
-                   'sMA==')
-
-challengeNonce = '178288758716122392881254770685'
-
-challengeResponse = ('digest',
-                     {'nonce': challengeNonce,
-                      'qop': 'auth', 'realm': 'test realm',
-                      'algorithm': 'md5',
-                      'opaque': challengeOpaque})
-
-cnonce = "29fc54aa1641c6fa0e151419361c8f23"
-
-authRequest1 = ('username="username", realm="test realm", nonce="%s", '
-                'uri="/write/", response="%s", opaque="%s", algorithm="md5", '
-                'cnonce="29fc54aa1641c6fa0e151419361c8f23", nc=00000001, '
-                'qop="auth"')
-
-authRequest2 = ('username="username", realm="test realm", nonce="%s", '
-                'uri="/write/", response="%s", opaque="%s", algorithm="md5", '
-                'cnonce="29fc54aa1641c6fa0e151419361c8f23", nc=00000002, '
-                'qop="auth"')
-
-namelessAuthRequest = 'realm="test realm",nonce="doesn\'t matter"'
-
-
-class DigestAuthTestCase(unittest.TestCase):
-    """
-    Test the behavior of DigestCredentialFactory
-    """
-
-    def setUp(self):
-        """
-        Create a DigestCredentialFactory for testing
-        """
-        self.credentialFactory = digest.DigestCredentialFactory('md5',
-                                                                'test realm')
-
-    def getDigestResponse(self, challenge, ncount):
-        """
-        Calculate the response for the given challenge
-        """
-        nonce = challenge.get('nonce')
-        algo = challenge.get('algorithm').lower()
-        qop = challenge.get('qop')
-
-        expected = digest.calcResponse(
-            digest.calcHA1(algo,
-                           "username",
-                           "test realm",
-                           "password",
-                           nonce,
-                           cnonce),
-            algo, nonce, ncount, cnonce, qop, "GET", "/write/", None
-            )
-        return expected
-
-    def test_getChallenge(self):
-        """
-        Test that all the required fields exist in the challenge,
-        and that the information matches what we put into our
-        DigestCredentialFactory
-        """
-        d = self.credentialFactory.getChallenge(clientAddress)
-        def _test(challenge):
-            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"))
-        return d.addCallback(_test)
-
-
-    def _createAndDecodeChallenge(self, chalID="00000001", req=_trivial_GET):
-        d = self.credentialFactory.getChallenge(clientAddress)
-        def _getChallenge(challenge):
-            return authRequest1 % (
-                challenge['nonce'],
-                self.getDigestResponse(challenge, chalID),
-                challenge['opaque'])
-        def _getResponse(clientResponse):
-            return self.credentialFactory.decode(clientResponse, req)
-        return d.addCallback(_getChallenge).addCallback(_getResponse)
-
-
-    def test_response(self):
-        """
-        Test that we can decode a valid response to our challenge
-        """
-        d = self._createAndDecodeChallenge()
-        def _test(creds):
-            self.failUnless(creds.checkPassword('password'))
-        return d.addCallback(_test)
-
-
-    def test_multiResponse(self):
-        """
-        Test that multiple responses to to a single challenge are handled
-        successfully.
-        """
-        d = self._createAndDecodeChallenge()
-        def _test(creds):
-            self.failUnless(creds.checkPassword('password'))
-        def _test2(_):
-            d2 = self._createAndDecodeChallenge("00000002")
-            return d2.addCallback(_test)
-        return d.addCallback(_test)
-
-
-    def test_failsWithDifferentMethod(self):
-        """
-        Test that the response fails if made for a different request method
-        than it is being issued for.
-        """
-        d = self._createAndDecodeChallenge(req=SimpleRequest(None, 'POST', '/'))
-        def _test(creds):
-            self.failIf(creds.checkPassword('password'))
-        return d.addCallback(_test)
-
-
-    def test_noUsername(self):
-        """
-        Test that login fails when our response does not contain a username,
-        or the username field is empty.
-        """
-
-        # Check for no username
-        e = self.assertRaises(error.LoginFailed,
-                              self.credentialFactory.decode,
-                              namelessAuthRequest,
-                              _trivial_GET)
-        self.assertEquals(str(e), "Invalid response, no username given.")
-
-        # Check for an empty username
-        e = self.assertRaises(error.LoginFailed,
-                              self.credentialFactory.decode,
-                              namelessAuthRequest + ',username=""',
-                              _trivial_GET)
-        self.assertEquals(str(e), "Invalid response, no username given.")
-
-    def test_noNonce(self):
-        """
-        Test that login fails when our response does not contain a nonce
-        """
-
-        e = self.assertRaises(error.LoginFailed,
-                              self.credentialFactory.decode,
-                              'realm="Test",username="Foo",opaque="bar"',
-                              _trivial_GET)
-        self.assertEquals(str(e), "Invalid response, no nonce given.")
-
-    def test_noOpaque(self):
-        """
-        Test that login fails when our response does not contain a nonce
-        """
-
-        e = self.assertRaises(error.LoginFailed,
-                              self.credentialFactory.decode,
-                              'realm="Test",username="Foo"',
-                              _trivial_GET)
-        self.assertEquals(str(e), "Invalid response, no opaque given.")
-
-    def test_checkHash(self):
-        """
-        Check that given a hash of the form 'username:realm:password'
-        we can verify the digest challenge
-        """
-        d = self._createAndDecodeChallenge()
-        def _test(creds):
-            self.failUnless(creds.checkHash(
-                    md5('username:test realm:password').hexdigest()))
-            self.failIf(creds.checkHash(
-                    md5('username:test realm:bogus').hexdigest()))
-        return d.addCallback(_test)
-
-
-    def test_invalidOpaque(self):
-        """
-        Test that login fails when the opaque does not contain all the required
-        parts.
-        """
-        credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
-
-        d = credentialFactory.getChallenge(clientAddress)
-        def _test(challenge):
-            self.assertRaises(
-                error.LoginFailed,
-                credentialFactory.verifyOpaque,
-                'badOpaque',
-                challenge['nonce'],
-                clientAddress.host)
-
-            badOpaque = ('foo-%s' % (
-                    'nonce,clientip'.encode('base64').strip('\n'),))
-
-            self.assertRaises(
-                error.LoginFailed,
-                credentialFactory.verifyOpaque,
-                badOpaque,
-                challenge['nonce'],
-                clientAddress.host)
-
-            self.assertRaises(
-                error.LoginFailed,
-                credentialFactory.verifyOpaque,
-                '',
-                challenge['nonce'],
-                clientAddress.host)
-
-        return d.addCallback(_test)
-
-
-    def test_incompatibleNonce(self):
-        """
-        Test that login fails when the given nonce from the response, does not
-        match the nonce encoded in the opaque.
-        """
-        credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
-
-        d = credentialFactory.getChallenge(clientAddress)
-        def _test(challenge):
-            badNonceOpaque = credentialFactory.generateOpaque(
-                '1234567890',
-                clientAddress.host)
-
-            self.assertRaises(
-                error.LoginFailed,
-                credentialFactory.verifyOpaque,
-                badNonceOpaque,
-                challenge['nonce'],
-                clientAddress.host)
-
-            self.assertRaises(
-                error.LoginFailed,
-                credentialFactory.verifyOpaque,
-                badNonceOpaque,
-                '',
-                clientAddress.host)
-        return d.addCallback(_test)
-
-
-    def test_incompatibleClientIp(self):
-        """
-        Test that the login fails when the request comes from a client ip
-        other than what is encoded in the opaque.
-        """
-        credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
-
-        d = credentialFactory.getChallenge(clientAddress)
-        def _test(challenge):
-            badNonceOpaque = credentialFactory.generateOpaque(
-                challenge['nonce'],
-                '10.0.0.1')
-
-            self.assertRaises(
-                error.LoginFailed,
-                credentialFactory.verifyOpaque,
-                badNonceOpaque,
-                challenge['nonce'],
-                clientAddress.host)
-        return d.addCallback(_test)
-
-
-    def test_oldNonce(self):
-        """
-        Test that the login fails when the given opaque is older than
-        DigestCredentialFactory.CHALLENGE_LIFETIME_SECS
-        """
-        credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
-
-        d = credentialFactory.getChallenge(clientAddress)
-        def _test(challenge):
-            key = '%s,%s,%s' % (challenge['nonce'],
-                                clientAddress.host,
-                                '-137876876')
-            digest = md5(key + credentialFactory.privateKey).hexdigest()
-            ekey = key.encode('base64')
-
-            oldNonceOpaque = '%s-%s' % (digest, ekey.strip('\n'))
-
-            self.assertRaises(
-                error.LoginFailed,
-                credentialFactory.verifyOpaque,
-                oldNonceOpaque,
-                challenge['nonce'],
-                clientAddress.host)
-
-        return d.addCallback(_test)
-
-
-    def test_mismatchedOpaqueChecksum(self):
-        """
-        Test that login fails when the opaque checksum fails verification
-        """
-        credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
-
-        d = credentialFactory.getChallenge(clientAddress)
-        def _test(challenge):
-            key = '%s,%s,%s' % (challenge['nonce'],
-                                clientAddress.host,
-                                '0')
-
-            digest = md5(key + 'this is not the right pkey').hexdigest()
-
-            badChecksum = '%s-%s' % (digest,
-                                     key.encode('base64').strip('\n'))
-
-            self.assertRaises(
-                error.LoginFailed,
-                credentialFactory.verifyOpaque,
-                badChecksum,
-                challenge['nonce'],
-                clientAddress.host)
-        return d.addCallback(_test)
-
-
-    def test_incompatibleCalcHA1Options(self):
-        """
-        Test that the appropriate error is raised when any of the
-        pszUsername, pszRealm, or pszPassword arguments are specified with
-        the preHA1 keyword argument.
-        """
-
-        arguments = (
-            ("user", "realm", "password", "preHA1"),
-            (None, "realm", None, "preHA1"),
-            (None, None, "password", "preHA1"),
-            )
-
-        for pszUsername, pszRealm, pszPassword, preHA1 in arguments:
-            self.assertRaises(
-                TypeError,
-                digest.calcHA1,
-                "md5",
-                pszUsername,
-                pszRealm,
-                pszPassword,
-                "nonce",
-                "cnonce",
-                preHA1=preHA1
-                )
-
-
-    def test_noNewlineOpaque(self):
-        """
-        L{digest.DigestCredentialFactory._generateOpaque} returns a value
-        without newlines, regardless of the length of the nonce.
-        """
-        opaque = self.credentialFactory.generateOpaque(
-            "long nonce " * 10, None)
-        self.assertNotIn('\n', opaque)
-
-
-
-from zope.interface import implements
-from twisted.cred import portal, checkers
-
-class TestHTTPUser(object):
-    """
-    Test avatar implementation for http auth with cred
-    """
-    implements(IHTTPUser)
-
-    username = None
-
-    def __init__(self, username):
-        """
-        @param username: The str username sent as part of the HTTP auth
-            response.
-        """
-        self.username = username
-
-
-class TestAuthRealm(object):
-    """
-    Test realm that supports the IHTTPUser interface
-    """
-
-    implements(portal.IRealm)
-
-    def requestAvatar(self, avatarId, mind, *interfaces):
-        if IHTTPUser in interfaces:
-            if avatarId == checkers.ANONYMOUS:
-                return IHTTPUser, TestHTTPUser('anonymous')
-
-            return IHTTPUser, TestHTTPUser(avatarId)
-
-        raise NotImplementedError("Only IHTTPUser interface is supported")
-
-
-class ProtectedResource(test_server.BaseTestResource):
-    """
-    A test resource for use with HTTPAuthWrapper that holds on to it's
-    request and segments so we can assert things about them.
-    """
-    addSlash = True
-
-    request = None
-    segments = None
-
-    def render(self, req):
-        self.request = req
-        return super(ProtectedResource, self).render(req)
-
-    def locateChild(self, req, segments):
-        self.segments = segments
-        return super(ProtectedResource, self).locateChild(req, segments)
-
-
-class NonAnonymousResource(test_server.BaseTestResource):
-    """
-    A resource that forces authentication by raising an
-    HTTPError with an UNAUTHORIZED code if the request is
-    an anonymous one.
-    """
-    addSlash = True
-
-    sendOwnHeaders = False
-
-    def render(self, req):
-        if req.avatar.username == 'anonymous':
-            if not self.sendOwnHeaders:
-                raise http.HTTPError(responsecode.UNAUTHORIZED)
-            else:
-                return http.Response(
-                    responsecode.UNAUTHORIZED,
-                    {'www-authenticate': [('basic', {'realm': 'foo'})]})
-        else:
-            return super(NonAnonymousResource, self).render(req)
-
-
-class HTTPAuthResourceTest(test_server.BaseCase):
-    """
-    Tests for the HTTPAuthWrapper Resource
-    """
-
-    def setUp(self):
-        """
-        Create a portal and add an in memory checker to it.
-
-        Then set up a protectedResource that will be wrapped in each test.
-        """
-        self.portal = portal.Portal(TestAuthRealm())
-        c = checkers.InMemoryUsernamePasswordDatabaseDontUse()
-        c.addUser('username', 'password')
-
-        self.portal.registerChecker(c)
-
-        self.credFactory = basic.BasicCredentialFactory('test realm')
-
-        self.protectedResource = ProtectedResource()
-        self.protectedResource.responseText = "You shouldn't see me."
-
-    def tearDown(self):
-        """
-        Clean up by getting rid of the portal, credentialFactory, and
-        protected resource
-        """
-        del self.portal
-        del self.credFactory
-        del self.protectedResource
-
-    def test_authenticatedRequest(self):
-        """
-        Test that after successful authentication the request provides
-        IAuthenticatedRequest and that the request.avatar implements
-        the proper interfaces for this realm and has the proper values
-        for this request.
-        """
-        self.protectedResource.responseText = "I hope you can see me."
-
-        root = wrapper.HTTPAuthResource(self.protectedResource,
-                                        [self.credFactory],
-                                        self.portal,
-                                        interfaces=(IHTTPUser,))
-
-        credentials = base64.encodestring('username:password')
-
-        d = self.assertResponse((root, 'http://localhost/',
-                                 {'authorization': ('basic', credentials)}),
-                                (200,
-                                 {}, 'I hope you can see me.'))
-
-        def checkRequest(result):
-            resource = self.protectedResource
-
-            self.failUnless(hasattr(resource, "request"))
-
-            request = resource.request
-
-            self.failUnless(IAuthenticatedRequest.providedBy(request))
-            self.failUnless(hasattr(request, "avatar"))
-            self.failUnless(IHTTPUser.providedBy(request.avatar))
-            self.failUnless(hasattr(request, "avatarInterface"))
-            self.assertEquals(request.avatarInterface, IHTTPUser)
-            self.assertEquals(request.avatar.username, 'username')
-
-        d.addCallback(checkRequest)
-        return d
-
-    def test_allowedMethods(self):
-        """
-        Test that unknown methods result in a 401 instead of a 405 when
-        authentication hasn't been completed.
-        """
-
-        self.method = 'PROPFIND'
-
-        root = wrapper.HTTPAuthResource(self.protectedResource,
-                                        [self.credFactory],
-                                        self.portal,
-                                        interfaces=(IHTTPUser,))
-        d = self.assertResponse(
-            (root, 'http://localhost/'),
-            (401,
-             {'WWW-Authenticate': [('basic',
-                                    {'realm': "test realm"})]},
-             None))
-
-        self.method = 'GET'
-
-        return d
-
-    def test_unauthorizedResponse(self):
-        """
-        Test that a request with no credentials results in a
-        valid Unauthorized response.
-        """
-        root = wrapper.HTTPAuthResource(self.protectedResource,
-                                        [self.credFactory],
-                                        self.portal,
-                                        interfaces=(IHTTPUser,))
-
-        def makeDeepRequest(res):
-            return self.assertResponse(
-                (root,
-                 'http://localhost/foo/bar/baz/bax'),
-                (401,
-                 {'WWW-Authenticate': [('basic',
-                                        {'realm': "test realm"})]},
-                 None))
-
-        d = self.assertResponse(
-            (root, 'http://localhost/'),
-            (401,
-             {'WWW-Authenticate': [('basic',
-                                    {'realm': "test realm"})]},
-             None))
-
-        return d.addCallback(makeDeepRequest)
-
-    def test_badCredentials(self):
-        """
-        Test that a request with bad credentials results in a valid
-        Unauthorized response
-        """
-        root = wrapper.HTTPAuthResource(self.protectedResource,
-                                        [self.credFactory],
-                                        self.portal,
-                                        interfaces=(IHTTPUser,))
-
-        credentials = base64.encodestring('bad:credentials')
-
-        d = self.assertResponse(
-            (root, 'http://localhost/',
-             {'authorization': [('basic', credentials)]}),
-            (401,
-             {'WWW-Authenticate': [('basic',
-                                    {'realm': "test realm"})]},
-             None))
-
-        return d
-
-    def test_successfulLogin(self):
-        """
-        Test that a request with good credentials results in the
-        appropriate response from the protected resource
-        """
-        self.protectedResource.responseText = "I hope you can see me."
-
-        root = wrapper.HTTPAuthResource(self.protectedResource,
-                                        [self.credFactory],
-                                        self.portal,
-                                        interfaces=(IHTTPUser,))
-
-        credentials = base64.encodestring('username:password')
-
-        d = self.assertResponse((root, 'http://localhost/',
-                                 {'authorization': ('basic', credentials)}),
-                                (200,
-                                 {}, 'I hope you can see me.'))
-
-        return d
-
-    def test_wrongScheme(self):
-        """
-        Test that a request with credentials for a scheme that is not
-        advertised by this resource results in the appropriate
-        unauthorized response.
-        """
-        root = wrapper.HTTPAuthResource(self.protectedResource,
-                                        [self.credFactory],
-                                        self.portal,
-                                        interfaces=(IHTTPUser,))
-
-        d = self.assertResponse((root, 'http://localhost/',
-                                 {'authorization':
-                                  [('digest',
-                                    'realm="foo", response="crap"')]}),
-                                (401,
-                                 {'www-authenticate':
-                                  [('basic', {'realm': 'test realm'})]},
-                                 None))
-
-        return d
-
-    def test_multipleWWWAuthenticateSchemes(self):
-        """
-        Test that our unauthorized response can contain challenges for
-        multiple authentication schemes.
-        """
-        root = wrapper.HTTPAuthResource(
-            self.protectedResource,
-            (basic.BasicCredentialFactory('test realm'),
-             FakeDigestCredentialFactory('md5', 'test realm')),
-            self.portal,
-            interfaces=(IHTTPUser,))
-
-        d = self.assertResponse((root, 'http://localhost/', {}),
-                                (401,
-                                 {'www-authenticate':
-                                  [challengeResponse,
-                                   ('basic', {'realm': 'test realm'})]},
-                                 None))
-
-        return d
-
-    def test_authorizationAgainstMultipleSchemes(self):
-        """
-        Test that we can successfully authenticate when presented
-        with multiple WWW-Authenticate headers
-        """
-
-        root = wrapper.HTTPAuthResource(
-            self.protectedResource,
-            (basic.BasicCredentialFactory('test realm'),
-             FakeDigestCredentialFactory('md5', 'test realm')),
-                                        self.portal,
-            interfaces=(IHTTPUser,))
-
-        def respondBasic(ign):
-            credentials = base64.encodestring('username:password')
-
-            d = self.assertResponse((root, 'http://localhost/',
-                                     {'authorization':
-                                        ('basic', credentials)}),
-                                    (200,
-                                     {}, None))
-
-            return d
-
-        def respond(ign):
-            d = self.assertResponse((root, 'http://localhost/',
-                                     {'authorization': authRequest1}),
-                                    (200,
-                                     {},
-                                     None))
-            return d.addCallback(respondBasic)
-
-        d = self.assertResponse((root, 'http://localhost/', {}),
-                                (401,
-                                 {'www-authenticate':
-                                  [challengeResponse,
-                                   ('basic', {'realm': 'test realm'})]},
-                                 None))
-
-        return d
-
-    def test_wrappedResourceGetsFullSegments(self):
-        """
-        Test that the wrapped resource gets all the URL segments in it's
-        locateChild.
-        """
-        self.protectedResource.responseText = "I hope you can see me."
-
-        root = wrapper.HTTPAuthResource(self.protectedResource,
-                                        [self.credFactory],
-                                        self.portal,
-                                        interfaces=(IHTTPUser,))
-
-        credentials = base64.encodestring('username:password')
-
-        d = self.assertResponse((root, 'http://localhost/foo/bar/baz/bax',
-                                 {'authorization': ('basic', credentials)}),
-                                (404,
-                                 {}, None))
-
-        def checkSegments(ign):
-            resource = self.protectedResource
-
-            self.assertEquals(resource.segments, ['foo', 'bar', 'baz', 'bax'])
-
-        d.addCallback(checkSegments)
-
-        return d
-
-    def test_invalidCredentials(self):
-        """
-        Malformed or otherwise invalid credentials (as determined by
-        the credential factory) should result in an Unauthorized response
-        """
-        root = wrapper.HTTPAuthResource(self.protectedResource,
-                                        [self.credFactory],
-                                        self.portal,
-                                        interfaces=(IHTTPUser,))
-
-        credentials = base64.encodestring('Not Good Credentials')
-
-        d = self.assertResponse((root, 'http://localhost/',
-                                 {'authorization': ('basic', credentials)}),
-                                (401,
-                                 {'WWW-Authenticate': [('basic',
-                                                        {'realm': "test realm"})]},
-                                 None))
-
-        return d
-
-    def test_anonymousAuthentication(self):
-        """
-        If our portal has a credentials checker for IAnonymous credentials
-        authentication succeeds if no Authorization header is present
-        """
-
-        self.portal.registerChecker(checkers.AllowAnonymousAccess())
-
-        self.protectedResource.responseText = "Anonymous access allowed"
-
-        root = wrapper.HTTPAuthResource(self.protectedResource,
-                                        [self.credFactory],
-                                        self.portal,
-                                        interfaces=(IHTTPUser,))
-
-        def _checkRequest(ign):
-            self.assertEquals(
-                self.protectedResource.request.avatar.username,
-                'anonymous')
-
-        d = self.assertResponse((root, 'http://localhost/',
-                                 {}),
-                                (200,
-                                 {},
-                                 "Anonymous access allowed"))
-        d.addCallback(_checkRequest)
-
-        return d
-
-    def test_forceAuthentication(self):
-        """
-        Test that if an HTTPError with an Unauthorized status code is raised
-        from within our protected resource, we add the WWW-Authenticate 
-        headers if they do not already exist.
-        """
-        self.portal.registerChecker(checkers.AllowAnonymousAccess())
-
-        nonAnonResource = NonAnonymousResource()
-        nonAnonResource.responseText = "We don't like anonymous users"
-
-        root = wrapper.HTTPAuthResource(nonAnonResource,
-                                        [self.credFactory],
-                                        self.portal,
-                                        interfaces = (IHTTPUser,))
-
-        def _tryAuthenticate(result):
-            credentials = base64.encodestring('username:password')
-
-            d2 = self.assertResponse(
-                (root, 'http://localhost/',
-                 {'authorization': ('basic', credentials)}),
-                (200,
-                 {},
-                 "We don't like anonymous users"))
-
-            return d2
-
-        d = self.assertResponse(
-            (root, 'http://localhost/',
-             {}),
-            (401,
-             {'WWW-Authenticate': [('basic',
-                                    {'realm': "test realm"})]},
-             None))
-
-        d.addCallback(_tryAuthenticate)
-
-        return d
-
-    def test_responseFilterDoesntClobberHeaders(self):
-        """
-        Test that if an UNAUTHORIZED response is returned and
-        already has 'WWW-Authenticate' headers we don't add them.
-        """
-        self.portal.registerChecker(checkers.AllowAnonymousAccess())
-
-        nonAnonResource = NonAnonymousResource()
-        nonAnonResource.responseText = "We don't like anonymous users"
-        nonAnonResource.sendOwnHeaders = True
-
-        root = wrapper.HTTPAuthResource(nonAnonResource,
-                                        [self.credFactory],
-                                        self.portal,
-                                        interfaces = (IHTTPUser,))
-
-        d = self.assertResponse(
-            (root, 'http://localhost/',
-             {}),
-            (401,
-             {'WWW-Authenticate': [('basic',
-                                    {'realm': "foo"})]},
-             None))
-
-        return d
-
-    def test_renderHTTP(self):
-        """
-        Test that if the renderHTTP method is ever called we authenticate
-        the request and delegate rendering to the wrapper.
-        """
-        self.protectedResource.responseText = "I hope you can see me."
-        self.protectedResource.addSlash = True
-
-        root = wrapper.HTTPAuthResource(self.protectedResource,
-                                        [self.credFactory],
-                                        self.portal,
-                                        interfaces = (IHTTPUser,))
-
-        request = SimpleRequest(None, "GET", "/")
-        request.prepath = ['']
-
-        def _gotSecondResponse(response):
-            self.assertEquals(response.code, 200)
-            self.assertEquals(str(response.stream.read()),
-                              "I hope you can see me.")
-
-        def _gotResponse(exception):
-            response = exception.response
-
-            self.assertEquals(response.code, 401)
-            self.failUnless(response.headers.hasHeader('WWW-Authenticate'))
-            self.assertEquals(response.headers.getHeader('WWW-Authenticate'),
-                              [('basic', {'realm': "test realm"})])
-
-            credentials = base64.encodestring('username:password')
-
-            request.headers.setHeader('authorization',
-                                      ['basic', credentials])
-
-            d = root.renderHTTP(request)
-            d.addCallback(_gotSecondResponse)
-
-        d = self.assertFailure(root.renderHTTP(request),
-                               http.HTTPError)
-
-        d.addCallback(_gotResponse)
-
-        return d

Copied: CalendarServer/trunk/twext/web2/test/test_httpauth.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_httpauth.py)
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_httpauth.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/test/test_httpauth.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,968 @@
+# Copyright (c) 2006-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.python.hashlib import md5
+from twisted.internet import address
+from twisted.trial import unittest
+from twisted.cred import error
+from twext.web2 import http, responsecode
+from twext.web2.auth import basic, digest, wrapper
+from twext.web2.auth.interfaces import IAuthenticatedRequest, IHTTPUser
+from twext.web2.test.test_server import SimpleRequest
+
+from twext.web2.test import test_server
+
+import base64
+
+_trivial_GET = SimpleRequest(None, 'GET', '/')
+
+class FakeDigestCredentialFactory(digest.DigestCredentialFactory):
+    """
+    A Fake Digest Credential Factory that generates a predictable
+    nonce and opaque
+    """
+
+    def __init__(self, *args, **kwargs):
+        super(FakeDigestCredentialFactory, self).__init__(*args, **kwargs)
+
+        self.privateKey = "0"
+
+    def generateNonce(self):
+        """
+        Generate a static nonce
+        """
+        return '178288758716122392881254770685'
+
+    def _getTime(self):
+        """
+        Return a stable time
+        """
+        return 0
+
+
+class BasicAuthTestCase(unittest.TestCase):
+    def setUp(self):
+        self.credentialFactory = basic.BasicCredentialFactory('foo')
+        self.username = 'dreid'
+        self.password = 'S3CuR1Ty'
+
+
+    def test_usernamePassword(self):
+        """
+        Test acceptance of username/password in basic auth.
+        """
+        response = base64.encodestring('%s:%s' % (
+                self.username,
+                self.password))
+
+        d = self.credentialFactory.decode(response, _trivial_GET)
+        return d.addCallback(
+            lambda creds: self.failUnless(creds.checkPassword(self.password)))
+
+
+    def test_incorrectPassword(self):
+        """
+        Incorrect passwords cause auth to fail.
+        """
+        response = base64.encodestring('%s:%s' % (
+                self.username,
+                'incorrectPassword'))
+
+        d = self.credentialFactory.decode(response, _trivial_GET)
+        return d.addCallback(
+            lambda creds: self.failIf(creds.checkPassword(self.password)))
+
+
+    def test_incorrectPadding(self):
+        """
+        Responses that have incorrect padding cause auth to fail.
+        """
+        response = base64.encodestring('%s:%s' % (
+                self.username,
+                self.password))
+
+        response = response.strip('=')
+
+        d = self.credentialFactory.decode(response, _trivial_GET)
+        def _test(creds):
+            self.failUnless(creds.checkPassword(self.password))
+        return d.addCallback(_test)
+
+
+    def test_invalidCredentials(self):
+        """
+        Auth attempts with no password should fail.
+        """
+        response = base64.encodestring(self.username)
+        d = self.credentialFactory.decode(response, _trivial_GET)
+        self.assertFailure(d, error.LoginFailed)
+
+
+clientAddress = address.IPv4Address('TCP', '127.0.0.1', 80)
+
+challengeOpaque = ('75c4bd95b96b7b7341c646c6502f0833-MTc4Mjg4NzU'
+                   '4NzE2MTIyMzkyODgxMjU0NzcwNjg1LHJlbW90ZWhvc3Q'
+                   'sMA==')
+
+challengeNonce = '178288758716122392881254770685'
+
+challengeResponse = ('digest',
+                     {'nonce': challengeNonce,
+                      'qop': 'auth', 'realm': 'test realm',
+                      'algorithm': 'md5',
+                      'opaque': challengeOpaque})
+
+cnonce = "29fc54aa1641c6fa0e151419361c8f23"
+
+authRequest1 = ('username="username", realm="test realm", nonce="%s", '
+                'uri="/write/", response="%s", opaque="%s", algorithm="md5", '
+                'cnonce="29fc54aa1641c6fa0e151419361c8f23", nc=00000001, '
+                'qop="auth"')
+
+authRequest2 = ('username="username", realm="test realm", nonce="%s", '
+                'uri="/write/", response="%s", opaque="%s", algorithm="md5", '
+                'cnonce="29fc54aa1641c6fa0e151419361c8f23", nc=00000002, '
+                'qop="auth"')
+
+namelessAuthRequest = 'realm="test realm",nonce="doesn\'t matter"'
+
+
+class DigestAuthTestCase(unittest.TestCase):
+    """
+    Test the behavior of DigestCredentialFactory
+    """
+
+    def setUp(self):
+        """
+        Create a DigestCredentialFactory for testing
+        """
+        self.credentialFactory = digest.DigestCredentialFactory('md5',
+                                                                'test realm')
+
+    def getDigestResponse(self, challenge, ncount):
+        """
+        Calculate the response for the given challenge
+        """
+        nonce = challenge.get('nonce')
+        algo = challenge.get('algorithm').lower()
+        qop = challenge.get('qop')
+
+        expected = digest.calcResponse(
+            digest.calcHA1(algo,
+                           "username",
+                           "test realm",
+                           "password",
+                           nonce,
+                           cnonce),
+            algo, nonce, ncount, cnonce, qop, "GET", "/write/", None
+            )
+        return expected
+
+    def test_getChallenge(self):
+        """
+        Test that all the required fields exist in the challenge,
+        and that the information matches what we put into our
+        DigestCredentialFactory
+        """
+        d = self.credentialFactory.getChallenge(clientAddress)
+        def _test(challenge):
+            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"))
+        return d.addCallback(_test)
+
+
+    def _createAndDecodeChallenge(self, chalID="00000001", req=_trivial_GET):
+        d = self.credentialFactory.getChallenge(clientAddress)
+        def _getChallenge(challenge):
+            return authRequest1 % (
+                challenge['nonce'],
+                self.getDigestResponse(challenge, chalID),
+                challenge['opaque'])
+        def _getResponse(clientResponse):
+            return self.credentialFactory.decode(clientResponse, req)
+        return d.addCallback(_getChallenge).addCallback(_getResponse)
+
+
+    def test_response(self):
+        """
+        Test that we can decode a valid response to our challenge
+        """
+        d = self._createAndDecodeChallenge()
+        def _test(creds):
+            self.failUnless(creds.checkPassword('password'))
+        return d.addCallback(_test)
+
+
+    def test_multiResponse(self):
+        """
+        Test that multiple responses to to a single challenge are handled
+        successfully.
+        """
+        d = self._createAndDecodeChallenge()
+        def _test(creds):
+            self.failUnless(creds.checkPassword('password'))
+        def _test2(_):
+            d2 = self._createAndDecodeChallenge("00000002")
+            return d2.addCallback(_test)
+        return d.addCallback(_test)
+
+
+    def test_failsWithDifferentMethod(self):
+        """
+        Test that the response fails if made for a different request method
+        than it is being issued for.
+        """
+        d = self._createAndDecodeChallenge(req=SimpleRequest(None, 'POST', '/'))
+        def _test(creds):
+            self.failIf(creds.checkPassword('password'))
+        return d.addCallback(_test)
+
+
+    def test_noUsername(self):
+        """
+        Test that login fails when our response does not contain a username,
+        or the username field is empty.
+        """
+
+        # Check for no username
+        e = self.assertRaises(error.LoginFailed,
+                              self.credentialFactory.decode,
+                              namelessAuthRequest,
+                              _trivial_GET)
+        self.assertEquals(str(e), "Invalid response, no username given.")
+
+        # Check for an empty username
+        e = self.assertRaises(error.LoginFailed,
+                              self.credentialFactory.decode,
+                              namelessAuthRequest + ',username=""',
+                              _trivial_GET)
+        self.assertEquals(str(e), "Invalid response, no username given.")
+
+    def test_noNonce(self):
+        """
+        Test that login fails when our response does not contain a nonce
+        """
+
+        e = self.assertRaises(error.LoginFailed,
+                              self.credentialFactory.decode,
+                              'realm="Test",username="Foo",opaque="bar"',
+                              _trivial_GET)
+        self.assertEquals(str(e), "Invalid response, no nonce given.")
+
+    def test_noOpaque(self):
+        """
+        Test that login fails when our response does not contain a nonce
+        """
+
+        e = self.assertRaises(error.LoginFailed,
+                              self.credentialFactory.decode,
+                              'realm="Test",username="Foo"',
+                              _trivial_GET)
+        self.assertEquals(str(e), "Invalid response, no opaque given.")
+
+    def test_checkHash(self):
+        """
+        Check that given a hash of the form 'username:realm:password'
+        we can verify the digest challenge
+        """
+        d = self._createAndDecodeChallenge()
+        def _test(creds):
+            self.failUnless(creds.checkHash(
+                    md5('username:test realm:password').hexdigest()))
+            self.failIf(creds.checkHash(
+                    md5('username:test realm:bogus').hexdigest()))
+        return d.addCallback(_test)
+
+
+    def test_invalidOpaque(self):
+        """
+        Test that login fails when the opaque does not contain all the required
+        parts.
+        """
+        credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
+
+        d = credentialFactory.getChallenge(clientAddress)
+        def _test(challenge):
+            self.assertRaises(
+                error.LoginFailed,
+                credentialFactory.verifyOpaque,
+                'badOpaque',
+                challenge['nonce'],
+                clientAddress.host)
+
+            badOpaque = ('foo-%s' % (
+                    'nonce,clientip'.encode('base64').strip('\n'),))
+
+            self.assertRaises(
+                error.LoginFailed,
+                credentialFactory.verifyOpaque,
+                badOpaque,
+                challenge['nonce'],
+                clientAddress.host)
+
+            self.assertRaises(
+                error.LoginFailed,
+                credentialFactory.verifyOpaque,
+                '',
+                challenge['nonce'],
+                clientAddress.host)
+
+        return d.addCallback(_test)
+
+
+    def test_incompatibleNonce(self):
+        """
+        Test that login fails when the given nonce from the response, does not
+        match the nonce encoded in the opaque.
+        """
+        credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
+
+        d = credentialFactory.getChallenge(clientAddress)
+        def _test(challenge):
+            badNonceOpaque = credentialFactory.generateOpaque(
+                '1234567890',
+                clientAddress.host)
+
+            self.assertRaises(
+                error.LoginFailed,
+                credentialFactory.verifyOpaque,
+                badNonceOpaque,
+                challenge['nonce'],
+                clientAddress.host)
+
+            self.assertRaises(
+                error.LoginFailed,
+                credentialFactory.verifyOpaque,
+                badNonceOpaque,
+                '',
+                clientAddress.host)
+        return d.addCallback(_test)
+
+
+    def test_incompatibleClientIp(self):
+        """
+        Test that the login fails when the request comes from a client ip
+        other than what is encoded in the opaque.
+        """
+        credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
+
+        d = credentialFactory.getChallenge(clientAddress)
+        def _test(challenge):
+            badNonceOpaque = credentialFactory.generateOpaque(
+                challenge['nonce'],
+                '10.0.0.1')
+
+            self.assertRaises(
+                error.LoginFailed,
+                credentialFactory.verifyOpaque,
+                badNonceOpaque,
+                challenge['nonce'],
+                clientAddress.host)
+        return d.addCallback(_test)
+
+
+    def test_oldNonce(self):
+        """
+        Test that the login fails when the given opaque is older than
+        DigestCredentialFactory.CHALLENGE_LIFETIME_SECS
+        """
+        credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
+
+        d = credentialFactory.getChallenge(clientAddress)
+        def _test(challenge):
+            key = '%s,%s,%s' % (challenge['nonce'],
+                                clientAddress.host,
+                                '-137876876')
+            digest = md5(key + credentialFactory.privateKey).hexdigest()
+            ekey = key.encode('base64')
+
+            oldNonceOpaque = '%s-%s' % (digest, ekey.strip('\n'))
+
+            self.assertRaises(
+                error.LoginFailed,
+                credentialFactory.verifyOpaque,
+                oldNonceOpaque,
+                challenge['nonce'],
+                clientAddress.host)
+
+        return d.addCallback(_test)
+
+
+    def test_mismatchedOpaqueChecksum(self):
+        """
+        Test that login fails when the opaque checksum fails verification
+        """
+        credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
+
+        d = credentialFactory.getChallenge(clientAddress)
+        def _test(challenge):
+            key = '%s,%s,%s' % (challenge['nonce'],
+                                clientAddress.host,
+                                '0')
+
+            digest = md5(key + 'this is not the right pkey').hexdigest()
+
+            badChecksum = '%s-%s' % (digest,
+                                     key.encode('base64').strip('\n'))
+
+            self.assertRaises(
+                error.LoginFailed,
+                credentialFactory.verifyOpaque,
+                badChecksum,
+                challenge['nonce'],
+                clientAddress.host)
+        return d.addCallback(_test)
+
+
+    def test_incompatibleCalcHA1Options(self):
+        """
+        Test that the appropriate error is raised when any of the
+        pszUsername, pszRealm, or pszPassword arguments are specified with
+        the preHA1 keyword argument.
+        """
+
+        arguments = (
+            ("user", "realm", "password", "preHA1"),
+            (None, "realm", None, "preHA1"),
+            (None, None, "password", "preHA1"),
+            )
+
+        for pszUsername, pszRealm, pszPassword, preHA1 in arguments:
+            self.assertRaises(
+                TypeError,
+                digest.calcHA1,
+                "md5",
+                pszUsername,
+                pszRealm,
+                pszPassword,
+                "nonce",
+                "cnonce",
+                preHA1=preHA1
+                )
+
+
+    def test_noNewlineOpaque(self):
+        """
+        L{digest.DigestCredentialFactory._generateOpaque} returns a value
+        without newlines, regardless of the length of the nonce.
+        """
+        opaque = self.credentialFactory.generateOpaque(
+            "long nonce " * 10, None)
+        self.assertNotIn('\n', opaque)
+
+
+
+from zope.interface import implements
+from twisted.cred import portal, checkers
+
+class TestHTTPUser(object):
+    """
+    Test avatar implementation for http auth with cred
+    """
+    implements(IHTTPUser)
+
+    username = None
+
+    def __init__(self, username):
+        """
+        @param username: The str username sent as part of the HTTP auth
+            response.
+        """
+        self.username = username
+
+
+class TestAuthRealm(object):
+    """
+    Test realm that supports the IHTTPUser interface
+    """
+
+    implements(portal.IRealm)
+
+    def requestAvatar(self, avatarId, mind, *interfaces):
+        if IHTTPUser in interfaces:
+            if avatarId == checkers.ANONYMOUS:
+                return IHTTPUser, TestHTTPUser('anonymous')
+
+            return IHTTPUser, TestHTTPUser(avatarId)
+
+        raise NotImplementedError("Only IHTTPUser interface is supported")
+
+
+class ProtectedResource(test_server.BaseTestResource):
+    """
+    A test resource for use with HTTPAuthWrapper that holds on to it's
+    request and segments so we can assert things about them.
+    """
+    addSlash = True
+
+    request = None
+    segments = None
+
+    def render(self, req):
+        self.request = req
+        return super(ProtectedResource, self).render(req)
+
+    def locateChild(self, req, segments):
+        self.segments = segments
+        return super(ProtectedResource, self).locateChild(req, segments)
+
+
+class NonAnonymousResource(test_server.BaseTestResource):
+    """
+    A resource that forces authentication by raising an
+    HTTPError with an UNAUTHORIZED code if the request is
+    an anonymous one.
+    """
+    addSlash = True
+
+    sendOwnHeaders = False
+
+    def render(self, req):
+        if req.avatar.username == 'anonymous':
+            if not self.sendOwnHeaders:
+                raise http.HTTPError(responsecode.UNAUTHORIZED)
+            else:
+                return http.Response(
+                    responsecode.UNAUTHORIZED,
+                    {'www-authenticate': [('basic', {'realm': 'foo'})]})
+        else:
+            return super(NonAnonymousResource, self).render(req)
+
+
+class HTTPAuthResourceTest(test_server.BaseCase):
+    """
+    Tests for the HTTPAuthWrapper Resource
+    """
+
+    def setUp(self):
+        """
+        Create a portal and add an in memory checker to it.
+
+        Then set up a protectedResource that will be wrapped in each test.
+        """
+        self.portal = portal.Portal(TestAuthRealm())
+        c = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+        c.addUser('username', 'password')
+
+        self.portal.registerChecker(c)
+
+        self.credFactory = basic.BasicCredentialFactory('test realm')
+
+        self.protectedResource = ProtectedResource()
+        self.protectedResource.responseText = "You shouldn't see me."
+
+    def tearDown(self):
+        """
+        Clean up by getting rid of the portal, credentialFactory, and
+        protected resource
+        """
+        del self.portal
+        del self.credFactory
+        del self.protectedResource
+
+    def test_authenticatedRequest(self):
+        """
+        Test that after successful authentication the request provides
+        IAuthenticatedRequest and that the request.avatar implements
+        the proper interfaces for this realm and has the proper values
+        for this request.
+        """
+        self.protectedResource.responseText = "I hope you can see me."
+
+        root = wrapper.HTTPAuthResource(self.protectedResource,
+                                        [self.credFactory],
+                                        self.portal,
+                                        interfaces=(IHTTPUser,))
+
+        credentials = base64.encodestring('username:password')
+
+        d = self.assertResponse((root, 'http://localhost/',
+                                 {'authorization': ('basic', credentials)}),
+                                (200,
+                                 {}, 'I hope you can see me.'))
+
+        def checkRequest(result):
+            resource = self.protectedResource
+
+            self.failUnless(hasattr(resource, "request"))
+
+            request = resource.request
+
+            self.failUnless(IAuthenticatedRequest.providedBy(request))
+            self.failUnless(hasattr(request, "avatar"))
+            self.failUnless(IHTTPUser.providedBy(request.avatar))
+            self.failUnless(hasattr(request, "avatarInterface"))
+            self.assertEquals(request.avatarInterface, IHTTPUser)
+            self.assertEquals(request.avatar.username, 'username')
+
+        d.addCallback(checkRequest)
+        return d
+
+    def test_allowedMethods(self):
+        """
+        Test that unknown methods result in a 401 instead of a 405 when
+        authentication hasn't been completed.
+        """
+
+        self.method = 'PROPFIND'
+
+        root = wrapper.HTTPAuthResource(self.protectedResource,
+                                        [self.credFactory],
+                                        self.portal,
+                                        interfaces=(IHTTPUser,))
+        d = self.assertResponse(
+            (root, 'http://localhost/'),
+            (401,
+             {'WWW-Authenticate': [('basic',
+                                    {'realm': "test realm"})]},
+             None))
+
+        self.method = 'GET'
+
+        return d
+
+    def test_unauthorizedResponse(self):
+        """
+        Test that a request with no credentials results in a
+        valid Unauthorized response.
+        """
+        root = wrapper.HTTPAuthResource(self.protectedResource,
+                                        [self.credFactory],
+                                        self.portal,
+                                        interfaces=(IHTTPUser,))
+
+        def makeDeepRequest(res):
+            return self.assertResponse(
+                (root,
+                 'http://localhost/foo/bar/baz/bax'),
+                (401,
+                 {'WWW-Authenticate': [('basic',
+                                        {'realm': "test realm"})]},
+                 None))
+
+        d = self.assertResponse(
+            (root, 'http://localhost/'),
+            (401,
+             {'WWW-Authenticate': [('basic',
+                                    {'realm': "test realm"})]},
+             None))
+
+        return d.addCallback(makeDeepRequest)
+
+    def test_badCredentials(self):
+        """
+        Test that a request with bad credentials results in a valid
+        Unauthorized response
+        """
+        root = wrapper.HTTPAuthResource(self.protectedResource,
+                                        [self.credFactory],
+                                        self.portal,
+                                        interfaces=(IHTTPUser,))
+
+        credentials = base64.encodestring('bad:credentials')
+
+        d = self.assertResponse(
+            (root, 'http://localhost/',
+             {'authorization': [('basic', credentials)]}),
+            (401,
+             {'WWW-Authenticate': [('basic',
+                                    {'realm': "test realm"})]},
+             None))
+
+        return d
+
+    def test_successfulLogin(self):
+        """
+        Test that a request with good credentials results in the
+        appropriate response from the protected resource
+        """
+        self.protectedResource.responseText = "I hope you can see me."
+
+        root = wrapper.HTTPAuthResource(self.protectedResource,
+                                        [self.credFactory],
+                                        self.portal,
+                                        interfaces=(IHTTPUser,))
+
+        credentials = base64.encodestring('username:password')
+
+        d = self.assertResponse((root, 'http://localhost/',
+                                 {'authorization': ('basic', credentials)}),
+                                (200,
+                                 {}, 'I hope you can see me.'))
+
+        return d
+
+    def test_wrongScheme(self):
+        """
+        Test that a request with credentials for a scheme that is not
+        advertised by this resource results in the appropriate
+        unauthorized response.
+        """
+        root = wrapper.HTTPAuthResource(self.protectedResource,
+                                        [self.credFactory],
+                                        self.portal,
+                                        interfaces=(IHTTPUser,))
+
+        d = self.assertResponse((root, 'http://localhost/',
+                                 {'authorization':
+                                  [('digest',
+                                    'realm="foo", response="crap"')]}),
+                                (401,
+                                 {'www-authenticate':
+                                  [('basic', {'realm': 'test realm'})]},
+                                 None))
+
+        return d
+
+    def test_multipleWWWAuthenticateSchemes(self):
+        """
+        Test that our unauthorized response can contain challenges for
+        multiple authentication schemes.
+        """
+        root = wrapper.HTTPAuthResource(
+            self.protectedResource,
+            (basic.BasicCredentialFactory('test realm'),
+             FakeDigestCredentialFactory('md5', 'test realm')),
+            self.portal,
+            interfaces=(IHTTPUser,))
+
+        d = self.assertResponse((root, 'http://localhost/', {}),
+                                (401,
+                                 {'www-authenticate':
+                                  [challengeResponse,
+                                   ('basic', {'realm': 'test realm'})]},
+                                 None))
+
+        return d
+
+    def test_authorizationAgainstMultipleSchemes(self):
+        """
+        Test that we can successfully authenticate when presented
+        with multiple WWW-Authenticate headers
+        """
+
+        root = wrapper.HTTPAuthResource(
+            self.protectedResource,
+            (basic.BasicCredentialFactory('test realm'),
+             FakeDigestCredentialFactory('md5', 'test realm')),
+                                        self.portal,
+            interfaces=(IHTTPUser,))
+
+        def respondBasic(ign):
+            credentials = base64.encodestring('username:password')
+
+            d = self.assertResponse((root, 'http://localhost/',
+                                     {'authorization':
+                                        ('basic', credentials)}),
+                                    (200,
+                                     {}, None))
+
+            return d
+
+        def respond(ign):
+            d = self.assertResponse((root, 'http://localhost/',
+                                     {'authorization': authRequest1}),
+                                    (200,
+                                     {},
+                                     None))
+            return d.addCallback(respondBasic)
+
+        d = self.assertResponse((root, 'http://localhost/', {}),
+                                (401,
+                                 {'www-authenticate':
+                                  [challengeResponse,
+                                   ('basic', {'realm': 'test realm'})]},
+                                 None))
+
+        return d
+
+    def test_wrappedResourceGetsFullSegments(self):
+        """
+        Test that the wrapped resource gets all the URL segments in it's
+        locateChild.
+        """
+        self.protectedResource.responseText = "I hope you can see me."
+
+        root = wrapper.HTTPAuthResource(self.protectedResource,
+                                        [self.credFactory],
+                                        self.portal,
+                                        interfaces=(IHTTPUser,))
+
+        credentials = base64.encodestring('username:password')
+
+        d = self.assertResponse((root, 'http://localhost/foo/bar/baz/bax',
+                                 {'authorization': ('basic', credentials)}),
+                                (404,
+                                 {}, None))
+
+        def checkSegments(ign):
+            resource = self.protectedResource
+
+            self.assertEquals(resource.segments, ['foo', 'bar', 'baz', 'bax'])
+
+        d.addCallback(checkSegments)
+
+        return d
+
+    def test_invalidCredentials(self):
+        """
+        Malformed or otherwise invalid credentials (as determined by
+        the credential factory) should result in an Unauthorized response
+        """
+        root = wrapper.HTTPAuthResource(self.protectedResource,
+                                        [self.credFactory],
+                                        self.portal,
+                                        interfaces=(IHTTPUser,))
+
+        credentials = base64.encodestring('Not Good Credentials')
+
+        d = self.assertResponse((root, 'http://localhost/',
+                                 {'authorization': ('basic', credentials)}),
+                                (401,
+                                 {'WWW-Authenticate': [('basic',
+                                                        {'realm': "test realm"})]},
+                                 None))
+
+        return d
+
+    def test_anonymousAuthentication(self):
+        """
+        If our portal has a credentials checker for IAnonymous credentials
+        authentication succeeds if no Authorization header is present
+        """
+
+        self.portal.registerChecker(checkers.AllowAnonymousAccess())
+
+        self.protectedResource.responseText = "Anonymous access allowed"
+
+        root = wrapper.HTTPAuthResource(self.protectedResource,
+                                        [self.credFactory],
+                                        self.portal,
+                                        interfaces=(IHTTPUser,))
+
+        def _checkRequest(ign):
+            self.assertEquals(
+                self.protectedResource.request.avatar.username,
+                'anonymous')
+
+        d = self.assertResponse((root, 'http://localhost/',
+                                 {}),
+                                (200,
+                                 {},
+                                 "Anonymous access allowed"))
+        d.addCallback(_checkRequest)
+
+        return d
+
+    def test_forceAuthentication(self):
+        """
+        Test that if an HTTPError with an Unauthorized status code is raised
+        from within our protected resource, we add the WWW-Authenticate 
+        headers if they do not already exist.
+        """
+        self.portal.registerChecker(checkers.AllowAnonymousAccess())
+
+        nonAnonResource = NonAnonymousResource()
+        nonAnonResource.responseText = "We don't like anonymous users"
+
+        root = wrapper.HTTPAuthResource(nonAnonResource,
+                                        [self.credFactory],
+                                        self.portal,
+                                        interfaces = (IHTTPUser,))
+
+        def _tryAuthenticate(result):
+            credentials = base64.encodestring('username:password')
+
+            d2 = self.assertResponse(
+                (root, 'http://localhost/',
+                 {'authorization': ('basic', credentials)}),
+                (200,
+                 {},
+                 "We don't like anonymous users"))
+
+            return d2
+
+        d = self.assertResponse(
+            (root, 'http://localhost/',
+             {}),
+            (401,
+             {'WWW-Authenticate': [('basic',
+                                    {'realm': "test realm"})]},
+             None))
+
+        d.addCallback(_tryAuthenticate)
+
+        return d
+
+    def test_responseFilterDoesntClobberHeaders(self):
+        """
+        Test that if an UNAUTHORIZED response is returned and
+        already has 'WWW-Authenticate' headers we don't add them.
+        """
+        self.portal.registerChecker(checkers.AllowAnonymousAccess())
+
+        nonAnonResource = NonAnonymousResource()
+        nonAnonResource.responseText = "We don't like anonymous users"
+        nonAnonResource.sendOwnHeaders = True
+
+        root = wrapper.HTTPAuthResource(nonAnonResource,
+                                        [self.credFactory],
+                                        self.portal,
+                                        interfaces = (IHTTPUser,))
+
+        d = self.assertResponse(
+            (root, 'http://localhost/',
+             {}),
+            (401,
+             {'WWW-Authenticate': [('basic',
+                                    {'realm': "foo"})]},
+             None))
+
+        return d
+
+    def test_renderHTTP(self):
+        """
+        Test that if the renderHTTP method is ever called we authenticate
+        the request and delegate rendering to the wrapper.
+        """
+        self.protectedResource.responseText = "I hope you can see me."
+        self.protectedResource.addSlash = True
+
+        root = wrapper.HTTPAuthResource(self.protectedResource,
+                                        [self.credFactory],
+                                        self.portal,
+                                        interfaces = (IHTTPUser,))
+
+        request = SimpleRequest(None, "GET", "/")
+        request.prepath = ['']
+
+        def _gotSecondResponse(response):
+            self.assertEquals(response.code, 200)
+            self.assertEquals(str(response.stream.read()),
+                              "I hope you can see me.")
+
+        def _gotResponse(exception):
+            response = exception.response
+
+            self.assertEquals(response.code, 401)
+            self.failUnless(response.headers.hasHeader('WWW-Authenticate'))
+            self.assertEquals(response.headers.getHeader('WWW-Authenticate'),
+                              [('basic', {'realm': "test realm"})])
+
+            credentials = base64.encodestring('username:password')
+
+            request.headers.setHeader('authorization',
+                                      ['basic', credentials])
+
+            d = root.renderHTTP(request)
+            d.addCallback(_gotSecondResponse)
+
+        d = self.assertFailure(root.renderHTTP(request),
+                               http.HTTPError)
+
+        d.addCallback(_gotResponse)
+
+        return d

Deleted: CalendarServer/trunk/twext/web2/test/test_log.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_log.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/test/test_log.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,148 +0,0 @@
-# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-from twext.web2 import log, resource, http
-from twext.web2.test.test_server import BaseCase, BaseTestResource
-
-from twisted.python import log as tlog
-
-class BufferingLogObserver(log.BaseCommonAccessLoggingObserver):
-    """
-    A web2 log observer that buffer messages.
-    """
-    messages = []
-    def logMessage(self, message):
-        self.messages.append(message)
-
-class SetDateWrapperResource(resource.WrapperResource):
-    """
-    A resource wrapper which sets the date header.
-    """
-    def hook(self, req):
-        def _filter(req, resp):
-            resp.headers.setHeader('date', 0.0)
-            return resp
-        _filter.handleErrors = True
-
-        req.addResponseFilter(_filter, atEnd=True)
-
-class NoneStreamResource(resource.Resource):
-    """
-    A basic empty resource.
-    """
-    def render(self, req):
-        return http.Response(200)
-
-class TestLogging(BaseCase):
-    def setUp(self):
-        self.blo = BufferingLogObserver()
-        tlog.addObserver(self.blo.emit)
-
-        # some default resource setup
-        self.resrc = BaseTestResource()
-        self.resrc.child_emptystream = NoneStreamResource()
-
-        self.root = SetDateWrapperResource(log.LogWrapperResource(self.resrc))
-
-    def tearDown(self):
-        tlog.removeObserver(self.blo.emit)
-
-    def assertLogged(self, **expected):
-        """
-        Check that logged messages matches expected format.
-        """
-        if 'date' not in expected:
-            epoch = log.BaseCommonAccessLoggingObserver().logDateString(0)
-            expected['date'] = epoch
-
-        if 'user' not in expected:
-            expected['user'] = '-'
-
-        if 'referer' not in expected:
-            expected['referer'] = '-'
-
-        if 'user-agent' not in expected:
-            expected['user-agent'] = '-'
-
-        if 'version' not in expected:
-            expected['version'] = '1.1'
-
-        if 'remotehost' not in expected:
-            expected['remotehost'] = 'remotehost'
-
-        messages = self.blo.messages[:]
-        del self.blo.messages[:]
-
-        expectedLog = ('%(remotehost)s - %(user)s [%(date)s] "%(method)s '
-                       '%(uri)s HTTP/%(version)s" %(status)d %(length)d '
-                       '"%(referer)s" "%(user-agent)s"')
-
-        if expected.get('logged', True):
-            # Ensure there weren't other messages hanging out
-            self.assertEquals(len(messages), 1, "len(%r) != 1" % (messages, ))
-            self.assertEquals(messages[0], expectedLog % expected)
-        else:
-            self.assertEquals(len(messages), 0, "len(%r) != 0" % (messages, ))
-
-    def test_logSimpleRequest(self):
-        """
-        Check the log for a simple request.
-        """
-        uri = 'http://localhost/'
-        method = 'GET'
-
-        def _cbCheckLog(response):
-            self.assertLogged(method=method, uri=uri, status=response[0],
-                              length=response[1].getHeader('content-length'))
-
-        d = self.getResponseFor(self.root, uri, method=method)
-        d.addCallback(_cbCheckLog)
-
-        return d
-
-    def test_logErrors(self):
-        """
-        Test the error log.
-        """
-        def test(_, uri, method, **expected):
-            expected['uri'] = uri
-            expected['method'] = method
-
-            def _cbCheckLog(response):
-                self.assertEquals(response[0], expected['status'])
-                self.assertLogged(
-                    length=response[1].getHeader('content-length'), **expected)
-
-            return self.getResponseFor(self.root,
-                                       uri,
-                                       method=method).addCallback(_cbCheckLog)
-
-
-        uri = 'http://localhost/foo' # doesn't exist
-        method = 'GET'
-
-        d = test(None, uri, method, status=404, logged=True)
-
-        # no host. this should result in a 400 which doesn't get logged
-        uri = 'http:///'
-
-        d.addCallback(test, uri, method, status=400, logged=False)
-
-        return d
-
-    def test_logNoneResponseStream(self):
-        """
-        Test the log of an empty resource.
-        """
-        uri = 'http://localhost/emptystream'
-        method = 'GET'
-
-        def _cbCheckLog(response):
-            self.assertLogged(method=method, uri=uri, status=200,
-                              length=0)
-
-        d = self.getResponseFor(self.root, uri, method=method)
-        d.addCallback(_cbCheckLog)
-
-        return d
-

Copied: CalendarServer/trunk/twext/web2/test/test_log.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_log.py)
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_log.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/test/test_log.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,148 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twext.web2 import log, resource, http
+from twext.web2.test.test_server import BaseCase, BaseTestResource
+
+from twisted.python import log as tlog
+
+class BufferingLogObserver(log.BaseCommonAccessLoggingObserver):
+    """
+    A web2 log observer that buffer messages.
+    """
+    messages = []
+    def logMessage(self, message):
+        self.messages.append(message)
+
+class SetDateWrapperResource(resource.WrapperResource):
+    """
+    A resource wrapper which sets the date header.
+    """
+    def hook(self, req):
+        def _filter(req, resp):
+            resp.headers.setHeader('date', 0.0)
+            return resp
+        _filter.handleErrors = True
+
+        req.addResponseFilter(_filter, atEnd=True)
+
+class NoneStreamResource(resource.Resource):
+    """
+    A basic empty resource.
+    """
+    def render(self, req):
+        return http.Response(200)
+
+class TestLogging(BaseCase):
+    def setUp(self):
+        self.blo = BufferingLogObserver()
+        tlog.addObserver(self.blo.emit)
+
+        # some default resource setup
+        self.resrc = BaseTestResource()
+        self.resrc.child_emptystream = NoneStreamResource()
+
+        self.root = SetDateWrapperResource(log.LogWrapperResource(self.resrc))
+
+    def tearDown(self):
+        tlog.removeObserver(self.blo.emit)
+
+    def assertLogged(self, **expected):
+        """
+        Check that logged messages matches expected format.
+        """
+        if 'date' not in expected:
+            epoch = log.BaseCommonAccessLoggingObserver().logDateString(0)
+            expected['date'] = epoch
+
+        if 'user' not in expected:
+            expected['user'] = '-'
+
+        if 'referer' not in expected:
+            expected['referer'] = '-'
+
+        if 'user-agent' not in expected:
+            expected['user-agent'] = '-'
+
+        if 'version' not in expected:
+            expected['version'] = '1.1'
+
+        if 'remotehost' not in expected:
+            expected['remotehost'] = 'remotehost'
+
+        messages = self.blo.messages[:]
+        del self.blo.messages[:]
+
+        expectedLog = ('%(remotehost)s - %(user)s [%(date)s] "%(method)s '
+                       '%(uri)s HTTP/%(version)s" %(status)d %(length)d '
+                       '"%(referer)s" "%(user-agent)s"')
+
+        if expected.get('logged', True):
+            # Ensure there weren't other messages hanging out
+            self.assertEquals(len(messages), 1, "len(%r) != 1" % (messages, ))
+            self.assertEquals(messages[0], expectedLog % expected)
+        else:
+            self.assertEquals(len(messages), 0, "len(%r) != 0" % (messages, ))
+
+    def test_logSimpleRequest(self):
+        """
+        Check the log for a simple request.
+        """
+        uri = 'http://localhost/'
+        method = 'GET'
+
+        def _cbCheckLog(response):
+            self.assertLogged(method=method, uri=uri, status=response[0],
+                              length=response[1].getHeader('content-length'))
+
+        d = self.getResponseFor(self.root, uri, method=method)
+        d.addCallback(_cbCheckLog)
+
+        return d
+
+    def test_logErrors(self):
+        """
+        Test the error log.
+        """
+        def test(_, uri, method, **expected):
+            expected['uri'] = uri
+            expected['method'] = method
+
+            def _cbCheckLog(response):
+                self.assertEquals(response[0], expected['status'])
+                self.assertLogged(
+                    length=response[1].getHeader('content-length'), **expected)
+
+            return self.getResponseFor(self.root,
+                                       uri,
+                                       method=method).addCallback(_cbCheckLog)
+
+
+        uri = 'http://localhost/foo' # doesn't exist
+        method = 'GET'
+
+        d = test(None, uri, method, status=404, logged=True)
+
+        # no host. this should result in a 400 which doesn't get logged
+        uri = 'http:///'
+
+        d.addCallback(test, uri, method, status=400, logged=False)
+
+        return d
+
+    def test_logNoneResponseStream(self):
+        """
+        Test the log of an empty resource.
+        """
+        uri = 'http://localhost/emptystream'
+        method = 'GET'
+
+        def _cbCheckLog(response):
+            self.assertLogged(method=method, uri=uri, status=200,
+                              length=0)
+
+        d = self.getResponseFor(self.root, uri, method=method)
+        d.addCallback(_cbCheckLog)
+
+        return d
+

Deleted: CalendarServer/trunk/twext/web2/test/test_resource.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_resource.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/test/test_resource.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,243 +0,0 @@
-# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-A test harness for twext.web2.resource.
-"""
-
-from sets import Set as set
-
-from zope.interface import implements
-
-from twisted.internet.defer import succeed, fail
-from twisted.trial import unittest
-from twext.web2 import responsecode
-from twext.web2.iweb import IResource
-from twext.web2.http import Response
-from twext.web2.stream import MemoryStream
-from twext.web2.resource import RenderMixin, LeafResource
-from twext.web2.server import Site, StopTraversal
-from twext.web2.test.test_server import SimpleRequest
-
-class PreconditionError (Exception):
-    "Precondition Failure"
-
-class TestResource (RenderMixin):
-    implements(IResource)
-
-    def _handler(self, request):
-        if request is None:
-            return responsecode.INTERNAL_SERVER_ERROR
-        return responsecode.NO_CONTENT
-
-    http_BLEARGH       = _handler
-    http_HUCKHUCKBLORP = _handler
-    http_SWEETHOOKUPS  = _handler
-    http_HOOKUPS       = _handler
-
-    def preconditions_BLEARGH(self, request):
-        raise PreconditionError()
-
-    def precondition_HUCKHUCKBLORP(self, request):
-        return fail(None)
-
-    def preconditions_SWEETHOOKUPS(self, request):
-        return None
-
-    def preconditions_HOOKUPS(self, request):
-        return succeed(None)
-
-    renderOutput = "Snootch to the hootch"
-
-    def render(self, request):
-        response = Response()
-        response.stream = MemoryStream(self.renderOutput)
-        return response
-
-def generateResponse(method):
-    resource = TestResource()
-    method = getattr(resource, "http_" + method)
-    return method(SimpleRequest(Site(resource), method, "/"))
-
-class RenderMixInTestCase (unittest.TestCase):
-    """
-    Test RenderMixin.
-    """
-    _my_allowed_methods = set((
-        "HEAD", "OPTIONS", "TRACE", "GET",
-        "BLEARGH", "HUCKHUCKBLORP",
-        "SWEETHOOKUPS", "HOOKUPS",
-    ))
-
-    def test_allowedMethods(self):
-        """
-        RenderMixin.allowedMethods()
-        """
-        self.assertEquals(
-            set(TestResource().allowedMethods()),
-            self._my_allowed_methods
-        )
-
-    def test_checkPreconditions_raises(self):
-        """
-        RenderMixin.checkPreconditions()
-        Exception raised in checkPreconditions()
-        """
-        resource = TestResource()
-        request = SimpleRequest(Site(resource), "BLEARGH", "/")
-
-        # Check that checkPreconditions raises as expected
-        self.assertRaises(PreconditionError, resource.checkPreconditions, request)
-
-        # Check that renderHTTP calls checkPreconditions
-        self.assertRaises(PreconditionError, resource.renderHTTP, request)
-
-    def test_checkPreconditions_none(self):
-        """
-        RenderMixin.checkPreconditions()
-        checkPreconditions() returns None
-        """
-        resource = TestResource()
-        request = SimpleRequest(Site(resource), "SWEETHOOKUPS", "/")
-
-        # Check that checkPreconditions without a raise doesn't barf
-        self.assertEquals(resource.renderHTTP(request), responsecode.NO_CONTENT)
-
-    def test_checkPreconditions_deferred(self):
-        """
-        RenderMixin.checkPreconditions()
-        checkPreconditions() returns a deferred
-        """
-        resource = TestResource()
-        request = SimpleRequest(Site(resource), "HOOKUPS", "/")
-
-        # Check that checkPreconditions without a raise doesn't barf
-        def checkResponse(response):
-            self.assertEquals(response, responsecode.NO_CONTENT)
-
-        d = resource.renderHTTP(request)
-        d.addCallback(checkResponse)
-
-    def test_OPTIONS_status(self):
-        """
-        RenderMixin.http_OPTIONS()
-        Response code is OK
-        """
-        response = generateResponse("OPTIONS")
-        self.assertEquals(response.code, responsecode.OK)
-
-    def test_OPTIONS_allow(self):
-        """
-        RenderMixin.http_OPTIONS()
-        Allow header indicates allowed methods
-        """
-        response = generateResponse("OPTIONS")
-        self.assertEquals(
-            set(response.headers.getHeader("allow")),
-            self._my_allowed_methods
-        )
-
-    def test_TRACE_status(self):
-        """
-        RenderMixin.http_TRACE()
-        Response code is OK
-        """
-        response = generateResponse("TRACE")
-        self.assertEquals(response.code, responsecode.OK)
-
-    def test_TRACE_body(self):
-        """
-        RenderMixin.http_TRACE()
-        Check body for traciness
-        """
-        raise NotImplementedError()
-
-    test_TRACE_body.todo = "Someone should write this test"
-
-    def test_HEAD_status(self):
-        """
-        RenderMixin.http_HEAD()
-        Response code is OK
-        """
-        response = generateResponse("HEAD")
-        self.assertEquals(response.code, responsecode.OK)
-
-    def test_HEAD_body(self):
-        """
-        RenderMixin.http_HEAD()
-        Check body is empty
-        """
-        response = generateResponse("HEAD")
-        self.assertEquals(response.stream.length, 0)
-
-    test_HEAD_body.todo = (
-        "http_HEAD is implemented in a goober way that "
-        "relies on the server code to clean up after it."
-    )
-
-    def test_GET_status(self):
-        """
-        RenderMixin.http_GET()
-        Response code is OK
-        """
-        response = generateResponse("GET")
-        self.assertEquals(response.code, responsecode.OK)
-
-    def test_GET_body(self):
-        """
-        RenderMixin.http_GET()
-        Check body is empty
-        """
-        response = generateResponse("GET")
-        self.assertEquals(
-            str(response.stream.read()),
-            TestResource.renderOutput
-        )
-
-class ResourceTestCase (unittest.TestCase):
-    """
-    Test Resource.
-    """
-    def test_addSlash(self):
-        # I think this would include a test of http_GET()
-        raise NotImplementedError()
-    test_addSlash.todo = "Someone should write this test"
-
-    def test_locateChild(self):
-        raise NotImplementedError()
-    test_locateChild.todo = "Someone should write this test"
-
-    def test_child_nonsense(self):
-        raise NotImplementedError()
-    test_child_nonsense.todo = "Someone should write this test"
-
-class PostableResourceTestCase (unittest.TestCase):
-    """
-    Test PostableResource.
-    """
-    def test_POST(self):
-        raise NotImplementedError()
-    test_POST.todo = "Someone should write this test"
-
-class LeafResourceTestCase (unittest.TestCase):
-    """
-    Test LeafResource.
-    """
-    def test_locateChild(self):
-        resource = LeafResource()
-        child, segments = (
-            resource.locateChild(
-                SimpleRequest(Site(resource), "GET", "/"),
-                ("", "foo"),
-            )
-        )
-        self.assertEquals(child, resource)
-        self.assertEquals(segments, StopTraversal)
-
-class WrapperResourceTestCase (unittest.TestCase):
-    """
-    Test WrapperResource.
-    """
-    def test_hook(self):
-        raise NotImplementedError()
-    test_hook.todo = "Someone should write this test"

Copied: CalendarServer/trunk/twext/web2/test/test_resource.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_resource.py)
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_resource.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/test/test_resource.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,243 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A test harness for twext.web2.resource.
+"""
+
+from sets import Set as set
+
+from zope.interface import implements
+
+from twisted.internet.defer import succeed, fail
+from twisted.trial import unittest
+from twext.web2 import responsecode
+from twext.web2.iweb import IResource
+from twext.web2.http import Response
+from twext.web2.stream import MemoryStream
+from twext.web2.resource import RenderMixin, LeafResource
+from twext.web2.server import Site, StopTraversal
+from twext.web2.test.test_server import SimpleRequest
+
+class PreconditionError (Exception):
+    "Precondition Failure"
+
+class TestResource (RenderMixin):
+    implements(IResource)
+
+    def _handler(self, request):
+        if request is None:
+            return responsecode.INTERNAL_SERVER_ERROR
+        return responsecode.NO_CONTENT
+
+    http_BLEARGH       = _handler
+    http_HUCKHUCKBLORP = _handler
+    http_SWEETHOOKUPS  = _handler
+    http_HOOKUPS       = _handler
+
+    def preconditions_BLEARGH(self, request):
+        raise PreconditionError()
+
+    def precondition_HUCKHUCKBLORP(self, request):
+        return fail(None)
+
+    def preconditions_SWEETHOOKUPS(self, request):
+        return None
+
+    def preconditions_HOOKUPS(self, request):
+        return succeed(None)
+
+    renderOutput = "Snootch to the hootch"
+
+    def render(self, request):
+        response = Response()
+        response.stream = MemoryStream(self.renderOutput)
+        return response
+
+def generateResponse(method):
+    resource = TestResource()
+    method = getattr(resource, "http_" + method)
+    return method(SimpleRequest(Site(resource), method, "/"))
+
+class RenderMixInTestCase (unittest.TestCase):
+    """
+    Test RenderMixin.
+    """
+    _my_allowed_methods = set((
+        "HEAD", "OPTIONS", "TRACE", "GET",
+        "BLEARGH", "HUCKHUCKBLORP",
+        "SWEETHOOKUPS", "HOOKUPS",
+    ))
+
+    def test_allowedMethods(self):
+        """
+        RenderMixin.allowedMethods()
+        """
+        self.assertEquals(
+            set(TestResource().allowedMethods()),
+            self._my_allowed_methods
+        )
+
+    def test_checkPreconditions_raises(self):
+        """
+        RenderMixin.checkPreconditions()
+        Exception raised in checkPreconditions()
+        """
+        resource = TestResource()
+        request = SimpleRequest(Site(resource), "BLEARGH", "/")
+
+        # Check that checkPreconditions raises as expected
+        self.assertRaises(PreconditionError, resource.checkPreconditions, request)
+
+        # Check that renderHTTP calls checkPreconditions
+        self.assertRaises(PreconditionError, resource.renderHTTP, request)
+
+    def test_checkPreconditions_none(self):
+        """
+        RenderMixin.checkPreconditions()
+        checkPreconditions() returns None
+        """
+        resource = TestResource()
+        request = SimpleRequest(Site(resource), "SWEETHOOKUPS", "/")
+
+        # Check that checkPreconditions without a raise doesn't barf
+        self.assertEquals(resource.renderHTTP(request), responsecode.NO_CONTENT)
+
+    def test_checkPreconditions_deferred(self):
+        """
+        RenderMixin.checkPreconditions()
+        checkPreconditions() returns a deferred
+        """
+        resource = TestResource()
+        request = SimpleRequest(Site(resource), "HOOKUPS", "/")
+
+        # Check that checkPreconditions without a raise doesn't barf
+        def checkResponse(response):
+            self.assertEquals(response, responsecode.NO_CONTENT)
+
+        d = resource.renderHTTP(request)
+        d.addCallback(checkResponse)
+
+    def test_OPTIONS_status(self):
+        """
+        RenderMixin.http_OPTIONS()
+        Response code is OK
+        """
+        response = generateResponse("OPTIONS")
+        self.assertEquals(response.code, responsecode.OK)
+
+    def test_OPTIONS_allow(self):
+        """
+        RenderMixin.http_OPTIONS()
+        Allow header indicates allowed methods
+        """
+        response = generateResponse("OPTIONS")
+        self.assertEquals(
+            set(response.headers.getHeader("allow")),
+            self._my_allowed_methods
+        )
+
+    def test_TRACE_status(self):
+        """
+        RenderMixin.http_TRACE()
+        Response code is OK
+        """
+        response = generateResponse("TRACE")
+        self.assertEquals(response.code, responsecode.OK)
+
+    def test_TRACE_body(self):
+        """
+        RenderMixin.http_TRACE()
+        Check body for traciness
+        """
+        raise NotImplementedError()
+
+    test_TRACE_body.todo = "Someone should write this test"
+
+    def test_HEAD_status(self):
+        """
+        RenderMixin.http_HEAD()
+        Response code is OK
+        """
+        response = generateResponse("HEAD")
+        self.assertEquals(response.code, responsecode.OK)
+
+    def test_HEAD_body(self):
+        """
+        RenderMixin.http_HEAD()
+        Check body is empty
+        """
+        response = generateResponse("HEAD")
+        self.assertEquals(response.stream.length, 0)
+
+    test_HEAD_body.todo = (
+        "http_HEAD is implemented in a goober way that "
+        "relies on the server code to clean up after it."
+    )
+
+    def test_GET_status(self):
+        """
+        RenderMixin.http_GET()
+        Response code is OK
+        """
+        response = generateResponse("GET")
+        self.assertEquals(response.code, responsecode.OK)
+
+    def test_GET_body(self):
+        """
+        RenderMixin.http_GET()
+        Check body is empty
+        """
+        response = generateResponse("GET")
+        self.assertEquals(
+            str(response.stream.read()),
+            TestResource.renderOutput
+        )
+
+class ResourceTestCase (unittest.TestCase):
+    """
+    Test Resource.
+    """
+    def test_addSlash(self):
+        # I think this would include a test of http_GET()
+        raise NotImplementedError()
+    test_addSlash.todo = "Someone should write this test"
+
+    def test_locateChild(self):
+        raise NotImplementedError()
+    test_locateChild.todo = "Someone should write this test"
+
+    def test_child_nonsense(self):
+        raise NotImplementedError()
+    test_child_nonsense.todo = "Someone should write this test"
+
+class PostableResourceTestCase (unittest.TestCase):
+    """
+    Test PostableResource.
+    """
+    def test_POST(self):
+        raise NotImplementedError()
+    test_POST.todo = "Someone should write this test"
+
+class LeafResourceTestCase (unittest.TestCase):
+    """
+    Test LeafResource.
+    """
+    def test_locateChild(self):
+        resource = LeafResource()
+        child, segments = (
+            resource.locateChild(
+                SimpleRequest(Site(resource), "GET", "/"),
+                ("", "foo"),
+            )
+        )
+        self.assertEquals(child, resource)
+        self.assertEquals(segments, StopTraversal)
+
+class WrapperResourceTestCase (unittest.TestCase):
+    """
+    Test WrapperResource.
+    """
+    def test_hook(self):
+        raise NotImplementedError()
+    test_hook.todo = "Someone should write this test"

Deleted: CalendarServer/trunk/twext/web2/test/test_server.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_server.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/test/test_server.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,878 +0,0 @@
-# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-A test harness for the twext.web2 server.
-"""
-
-from zope.interface import implements
-
-from twisted.python import components
-from twext.web2 import http, http_headers, iweb, server
-from twext.web2 import resource, stream, compat
-from twisted.trial import unittest
-from twisted.internet import reactor, defer, address
-
-
-
-class NotResource(object):
-    """
-    Class which does not implement IResource.
-
-    Used as an adaptee by L{AdaptionTestCase.test_registered} to test that
-    if an object which does not provide IResource is adapted to IResource
-    and there is an adapter to IResource registered, that adapter is used.
-    """
-
-
-
-class ResourceAdapter(object):
-    """
-    Adapter to IResource.
-
-    Registered as an adapter from NotResource to IResource so that
-    L{AdaptionTestCase.test_registered} can test that such an adapter will
-    be used.
-    """
-    implements(iweb.IResource)
-
-    def __init__(self, original):
-        pass
-
-components.registerAdapter(ResourceAdapter, NotResource, iweb.IResource)
-
-
-
-class NotOldResource(object):
-    """
-    Class which does not implement IOldNevowResource or IResource.
-
-    Used as an adaptee by L{AdaptionTestCase.test_transitive} to test that
-    if an object which does not provide IResource or IOldNevowResource is
-    adapted to IResource and there is an adapter to IOldNevowResource
-    registered, first that adapter is used, then the included adapter from
-    IOldNevowResource to IResource is used.
-    """
-
-
-
-class OldResourceAdapter(object):
-    """
-    Adapter to IOldNevowResource.
-
-    Registered as an adapter from NotOldResource to IOldNevowResource so
-    that L{AdaptionTestCase.test_transitive} can test that such an adapter
-    will be used to allow the initial input to be adapted to IResource.
-    """
-    implements(iweb.IOldNevowResource)
-
-    def __init__(self, original):
-        pass
-
-components.registerAdapter(OldResourceAdapter, NotOldResource, iweb.IOldNevowResource)
-
-
-
-class AdaptionTestCase(unittest.TestCase):
-    """
-    Test the adaption of various objects to IResource.
-
-    Necessary due to the special implementation of __call__ on IResource
-    which extends the behavior provided by the base Interface.__call__.
-    """
-    def test_unadaptable(self):
-        """
-        Test that attempting to adapt to IResource an object not adaptable
-        to IResource raises an exception or returns the specified alternate
-        object.
-        """
-        class Unadaptable(object):
-            pass
-        self.assertRaises(TypeError, iweb.IResource, Unadaptable())
-        alternate = object()
-        self.assertIdentical(iweb.IResource(Unadaptable(), alternate), alternate)
-
-
-    def test_redundant(self):
-        """
-        Test that the adaption to IResource of an object which provides
-        IResource returns the same object.
-        """
-        class Resource(object):
-            implements(iweb.IResource)
-        resource = Resource()
-        self.assertIdentical(iweb.IResource(resource), resource)
-
-
-    def test_registered(self):
-        """
-        Test that if an adapter exists which can provide IResource for an
-        object which does not provide it, that adapter is used.
-        """
-        notResource = NotResource()
-        self.failUnless(isinstance(iweb.IResource(notResource), ResourceAdapter))
-
-
-    def test_oldResources(self):
-        """
-        Test that providers of L{IOldNevowResource} can be adapted to
-        IResource automatically.
-        """
-        class OldResource(object):
-            implements(iweb.IOldNevowResource)
-        oldResource = OldResource()
-        resource = iweb.IResource(oldResource)
-        self.failUnless(isinstance(resource, compat.OldNevowResourceAdapter))
-
-
-    def test_transitive(self):
-        """
-        Test that a special-case transitive adaption from something to
-        IOldNevowResource to IResource is possible.
-        """
-        notResource = NotOldResource()
-        resource = iweb.IResource(notResource)
-        self.failUnless(isinstance(resource, compat.OldNevowResourceAdapter))
-
-
-
-class SimpleRequest(server.Request):
-    """I can be used in cases where a Request object is necessary
-    but it is benificial to bypass the chanRequest
-    """
-
-    clientproto = (1,1)
-
-    def __init__(self, site, method, uri, headers=None, content=None):
-        if not headers:
-            headers = http_headers.Headers(headers)
-
-        super(SimpleRequest, self).__init__(
-            site=site,
-            chanRequest=None,
-            command=method,
-            path=uri,
-            version=self.clientproto,
-            contentLength=len(content or ''),
-            headers=headers)
-
-        self.stream = stream.MemoryStream(content or '')
-
-        self.remoteAddr = address.IPv4Address('TCP', '127.0.0.1', 0)
-        self._parseURL()
-        self.host = 'localhost'
-        self.port = 8080
-
-    def writeResponse(self, response):
-        return response
-
-
-class TestChanRequest:
-    implements(iweb.IChanRequest)
-
-    hostInfo = address.IPv4Address('TCP', 'host', 80), False
-    remoteHost = address.IPv4Address('TCP', 'remotehost', 34567)
-
-
-    def __init__(self, site, method, prepath, uri, length=None,
-                 headers=None, version=(1,1), content=None):
-        self.site = site
-        self.method = method
-        self.prepath = prepath
-        self.uri = uri
-        if headers is None:
-            headers = http_headers.Headers()
-        self.headers = headers
-        self.http_version = version
-        # Anything below here we do not pass as arguments
-        self.request = server.Request(self,
-                                      self.method,
-                                      self.uri,
-                                      self.http_version,
-                                      length,
-                                      self.headers,
-                                      site=self.site,
-                                      prepathuri=self.prepath)
-
-        if content is not None:
-            self.request.handleContentChunk(content)
-            self.request.handleContentComplete()
-
-        self.code = None
-        self.responseHeaders = None
-        self.data = ''
-        self.deferredFinish = defer.Deferred()
-
-    def writeIntermediateResponse(code, headers=None):
-        pass
-
-    def writeHeaders(self, code, headers):
-        self.responseHeaders = headers
-        self.code = code
-
-    def write(self, data):
-        self.data += data
-
-    def finish(self, failed=False):
-        result = self.code, self.responseHeaders, self.data, failed
-        self.finished = True
-        self.deferredFinish.callback(result)
-
-    def abortConnection(self):
-        self.finish(failed=True)
-
-    def registerProducer(self, producer, streaming):
-        pass
-
-    def unregisterProducer(self):
-        pass
-
-    def getHostInfo(self):
-        return self.hostInfo
-
-    def getRemoteHost(self):
-        return self.remoteHost
-
-
-class BaseTestResource(resource.Resource):
-    responseCode = 200
-    responseText = 'This is a fake resource.'
-    responseHeaders = {}
-    addSlash = False
-
-    def __init__(self, children=[]):
-        """
-        @type children: C{list} of C{tuple}
-        @param children: a list of ('path', resource) tuples
-        """
-        for i in children:
-            self.putChild(i[0], i[1])
-
-    def render(self, req):
-        return http.Response(self.responseCode, headers=self.responseHeaders,
-                             stream=self.responseStream())
-
-    def responseStream(self):
-        return stream.MemoryStream(self.responseText)
-
-
-
-_unset = object()
-class BaseCase(unittest.TestCase):
-    """
-    Base class for test cases that involve testing the result
-    of arbitrary HTTP(S) queries.
-    """
-
-    method = 'GET'
-    version = (1, 1)
-    wait_timeout = 5.0
-
-    def chanrequest(self, root, uri, length, headers, method, version, prepath, content):
-        site = server.Site(root)
-        return TestChanRequest(site, method, prepath, uri, length, headers, version, content)
-
-    def getResponseFor(self, root, uri, headers={},
-                       method=None, version=None, prepath='', content=None, length=_unset):
-        if not isinstance(headers, http_headers.Headers):
-            headers = http_headers.Headers(headers)
-        if length is _unset:
-            if content is not None:
-                length = len(content)
-            else:
-                length = 0
-
-        if method is None:
-            method = self.method
-        if version is None:
-            version = self.version
-
-        cr = self.chanrequest(root, uri, length, headers, method, version, prepath, content)
-        cr.request.process()
-        return cr.deferredFinish
-
-    def assertResponse(self, request_data, expected_response, failure=False):
-        """
-        @type request_data: C{tuple}
-        @type expected_response: C{tuple}
-        @param request_data: A tuple of arguments to pass to L{getResponseFor}:
-                             (root, uri, headers, method, version, prepath).
-                             Root resource and requested URI are required,
-                             and everything else is optional.
-        @param expected_response: A 3-tuple of the expected response:
-                                  (responseCode, headers, htmlData)
-        """
-        d = self.getResponseFor(*request_data)
-        d.addCallback(self._cbGotResponse, expected_response, failure)
-
-        return d
-
-    def _cbGotResponse(self, (code, headers, data, failed), expected_response, expectedfailure=False):
-        expected_code, expected_headers, expected_data = expected_response
-        self.assertEquals(code, expected_code)
-        if expected_data is not None:
-            self.assertEquals(data, expected_data)
-        for key, value in expected_headers.iteritems():
-            self.assertEquals(headers.getHeader(key), value)
-        self.assertEquals(failed, expectedfailure)
-
-
-
-class SampleWebTest(BaseCase):
-    class SampleTestResource(BaseTestResource):
-        addSlash = True
-        def child_validChild(self, req):
-            f = BaseTestResource()
-            f.responseCode = 200
-            f.responseText = 'This is a valid child resource.'
-            return f
-
-        def child_missingChild(self, req):
-            f = BaseTestResource()
-            f.responseCode = 404
-            f.responseStream = lambda self: None
-            return f
-
-        def child_remoteAddr(self, req):
-            f = BaseTestResource()
-            f.responseCode = 200
-            f.responseText = 'Remote Addr: %r' % req.remoteAddr.host
-            return f
-
-    def setUp(self):
-        self.root = self.SampleTestResource()
-
-    def test_root(self):
-        return self.assertResponse(
-            (self.root, 'http://host/'),
-            (200, {}, 'This is a fake resource.'))
-
-    def test_validChild(self):
-        return self.assertResponse(
-            (self.root, 'http://host/validChild'),
-            (200, {}, 'This is a valid child resource.'))
-
-    def test_invalidChild(self):
-        return self.assertResponse(
-            (self.root, 'http://host/invalidChild'),
-            (404, {}, None))
-
-    def test_remoteAddrExposure(self):
-        return self.assertResponse(
-            (self.root, 'http://host/remoteAddr'),
-            (200, {}, "Remote Addr: 'remotehost'"))
-
-    def test_leafresource(self):
-        class TestResource(resource.LeafResource):
-            def render(self, req):
-                return http.Response(stream="prepath:%s postpath:%s" % (
-                        req.prepath,
-                        req.postpath))
-
-        return self.assertResponse(
-            (TestResource(), 'http://host/consumed/path/segments'),
-            (200, {}, "prepath:[] postpath:['consumed', 'path', 'segments']"))
-
-    def test_redirectResource(self):
-        redirectResource = resource.RedirectResource(scheme='https',
-                                                     host='localhost',
-                                                     port=443,
-                                                     path='/foo',
-                                                     querystring='bar=baz')
-
-        return self.assertResponse(
-            (redirectResource, 'http://localhost/'),
-            (301, {'location': 'https://localhost/foo?bar=baz'}, None))
-
-
-class URLParsingTest(BaseCase):
-    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 test_normal(self):
-        return self.assertResponse(
-            (self.root, '/path', {'Host':'host'}),
-            (200, {}, 'Host:host, Path:/path'))
-
-    def test_fullurl(self):
-        return self.assertResponse(
-            (self.root, 'http://host/path'),
-            (200, {}, 'Host:host, Path:/path'))
-
-    def test_strangepath(self):
-        # Ensure that the double slashes don't confuse it
-        return self.assertResponse(
-            (self.root, '//path', {'Host':'host'}),
-            (200, {}, 'Host:host, Path://path'))
-
-    def test_strangepathfull(self):
-        return self.assertResponse(
-            (self.root, 'http://host//path'),
-            (200, {}, 'Host:host, Path://path'))
-
-
-
-class TestDeferredRendering(BaseCase):
-    class ResourceWithDeferreds(BaseTestResource):
-        addSlash=True
-        responseText = 'I should be wrapped in a Deferred.'
-        def render(self, req):
-            d = defer.Deferred()
-            reactor.callLater(
-                0, d.callback, BaseTestResource.render(self, req))
-            return d
-
-        def child_deferred(self, req):
-            d = defer.Deferred()
-            reactor.callLater(0, d.callback, BaseTestResource())
-            return d
-
-    def test_deferredRootResource(self):
-        return self.assertResponse(
-            (self.ResourceWithDeferreds(), 'http://host/'),
-            (200, {}, 'I should be wrapped in a Deferred.'))
-
-    def test_deferredChild(self):
-        return self.assertResponse(
-            (self.ResourceWithDeferreds(), 'http://host/deferred'),
-            (200, {}, 'This is a fake resource.'))
-
-
-
-class RedirectResourceTest(BaseCase):
-    def html(url):
-        return "<html><head><title>Moved Permanently</title></head><body><h1>Moved Permanently</h1><p>Document moved to %s.</p></body></html>" % (url,)
-    html = staticmethod(html)
-
-    def test_noRedirect(self):
-        # This is useless, since it's a loop, but hey
-        ds = []
-        for url in ("http://host/", "http://host/foo"):
-            ds.append(self.assertResponse(
-                (resource.RedirectResource(), url),
-                (301, {"location": url}, self.html(url))
-            ))
-        return defer.DeferredList(ds, fireOnOneErrback=True)
-
-    def test_hostRedirect(self):
-        ds = []
-        for url1, url2 in (
-            ("http://host/", "http://other/"),
-            ("http://host/foo", "http://other/foo"),
-        ):
-            ds.append(self.assertResponse(
-                (resource.RedirectResource(host="other"), url1),
-                (301, {"location": url2}, self.html(url2))
-            ))
-        return defer.DeferredList(ds, fireOnOneErrback=True)
-
-    def test_pathRedirect(self):
-        root = BaseTestResource()
-        redirect = resource.RedirectResource(path="/other")
-        root.putChild("r", redirect)
-
-        ds = []
-        for url1, url2 in (
-            ("http://host/r", "http://host/other"),
-            ("http://host/r/foo", "http://host/other"),
-        ):
-            ds.append(self.assertResponse(
-                (resource.RedirectResource(path="/other"), url1),
-                (301, {"location": url2}, self.html(url2))
-            ))
-        return defer.DeferredList(ds, fireOnOneErrback=True)
-
-
-
-class EmptyResource(resource.Resource):
-    def __init__(self, test):
-        self.test = test
-
-    def render(self, request):
-        self.test.assertEquals(request.urlForResource(self), self.expectedURI)
-        return 201
-
-
-
-class RememberURIs(BaseCase):
-    """
-    Tests for URI memory and lookup mechanism in server.Request.
-    """
-    def test_requestedResource(self):
-        """
-        Test urlForResource() on deeply nested resource looked up via
-        request processing.
-        """
-        root = EmptyResource(self)
-        root.expectedURI = "/"
-
-        foo = EmptyResource(self)
-        foo.expectedURI = "/foo"
-        root.putChild("foo", foo)
-
-        bar = EmptyResource(self)
-        bar.expectedURI = foo.expectedURI + "/bar"
-        foo.putChild("bar", bar)
-
-        baz = EmptyResource(self)
-        baz.expectedURI = bar.expectedURI + "/baz"
-        bar.putChild("baz", baz)
-
-        ds = []
-
-        for uri in (foo.expectedURI, bar.expectedURI, baz.expectedURI):
-            ds.append(self.assertResponse(
-                (root, uri, {'Host':'host'}),
-                (201, {}, None),
-            ))
-
-        return defer.DeferredList(ds, fireOnOneErrback=True)
-
-    def test_urlEncoding(self):
-        """
-        Test to make sure that URL encoding is working.
-        """
-        root = EmptyResource(self)
-        root.expectedURI = "/"
-
-        child = EmptyResource(self)
-        child.expectedURI = "/foo%20bar"
-
-        root.putChild("foo bar", child)
-
-        return self.assertResponse(
-            (root, child.expectedURI, {'Host':'host'}),
-            (201, {}, None)
-        )
-
-    def test_locateResource(self):
-        """
-        Test urlForResource() on resource looked up via a locateResource() call.
-        """
-        root = resource.Resource()
-        child = resource.Resource()
-        root.putChild("foo", child)
-
-        request = SimpleRequest(server.Site(root), "GET", "/")
-
-        def gotResource(resource):
-            self.assertEquals("/foo", request.urlForResource(resource))
-
-        d = defer.maybeDeferred(request.locateResource, "/foo")
-        d.addCallback(gotResource)
-        return d
-
-    def test_unknownResource(self):
-        """
-        Test urlForResource() on unknown resource.
-        """
-        root = resource.Resource()
-        child = resource.Resource()
-        request = SimpleRequest(server.Site(root), "GET", "/")
-
-        self.assertRaises(server.NoURLForResourceError, request.urlForResource, child)
-
-    def test_locateChildResource(self):
-        """
-        Test urlForResource() on deeply nested resource looked up via
-        locateChildResource().
-        """
-        root = EmptyResource(self)
-        root.expectedURI = "/"
-
-        foo = EmptyResource(self)
-        foo.expectedURI = "/foo"
-        root.putChild("foo", foo)
-
-        bar = EmptyResource(self)
-        bar.expectedURI = "/foo/bar"
-        foo.putChild("bar", bar)
-
-        baz = EmptyResource(self)
-        baz.expectedURI = "/foo/bar/b%20a%20z"
-        bar.putChild("b a z", baz)
-
-        request = SimpleRequest(server.Site(root), "GET", "/")
-
-        def gotResource(resource):
-            # Make sure locateChildResource() gave us the right answer
-            self.assertEquals(resource, bar)
-
-            return request.locateChildResource(resource, "b a z").addCallback(gotChildResource)
-
-        def gotChildResource(resource):
-            # Make sure locateChildResource() gave us the right answer
-            self.assertEquals(resource, baz)
-
-            self.assertEquals(resource.expectedURI, request.urlForResource(resource))
-
-        d = request.locateResource(bar.expectedURI)
-        d.addCallback(gotResource)
-        return d
-
-    def test_deferredLocateChild(self):
-        """
-        Test deferred value from locateChild()
-        """
-        class DeferredLocateChild(resource.Resource):
-            def locateChild(self, req, segments):
-                return defer.maybeDeferred(
-                    super(DeferredLocateChild, self).locateChild,
-                    req, segments
-                )
-
-        root = DeferredLocateChild()
-        child = resource.Resource()
-        root.putChild("foo", child)
-
-        request = SimpleRequest(server.Site(root), "GET", "/foo")
-
-        def gotResource(resource):
-            self.assertEquals("/foo", request.urlForResource(resource))
-
-        d = request.locateResource("/foo")
-        d.addCallback(gotResource)
-        return d
-
-
-
-class ParsePostDataTests(unittest.TestCase):
-    """
-    Tests for L{server.parsePOSTData}.
-    """
-
-    def test_noData(self):
-        """
-        Parsing a request without data should succeed but should not fill the
-        C{args} and C{files} attributes of the request.
-        """
-        root = resource.Resource()
-        request = SimpleRequest(server.Site(root), "GET", "/")
-        def cb(ign):
-            self.assertEquals(request.args, {})
-            self.assertEquals(request.files, {})
-        return server.parsePOSTData(request).addCallback(cb)
-
-
-    def test_noContentType(self):
-        """
-        Parsing a request without content-type should succeed but should not
-        fill the C{args} and C{files} attributes of the request.
-        """
-        root = resource.Resource()
-        request = SimpleRequest(server.Site(root), "GET", "/", content="foo")
-        def cb(ign):
-            self.assertEquals(request.args, {})
-            self.assertEquals(request.files, {})
-        return server.parsePOSTData(request).addCallback(cb)
-
-
-    def test_urlencoded(self):
-        """
-        Test parsing data in urlencoded format: it should end in the C{args}
-        attribute.
-        """
-        ctype = http_headers.MimeType('application', 'x-www-form-urlencoded')
-        content = "key=value&multiple=two+words&multiple=more%20words"
-        root = resource.Resource()
-        request = SimpleRequest(server.Site(root), "GET", "/",
-                http_headers.Headers({'content-type': ctype}), content)
-        def cb(ign):
-            self.assertEquals(request.files, {})
-            self.assertEquals(request.args,
-                {'multiple': ['two words', 'more words'], 'key': ['value']})
-        return server.parsePOSTData(request).addCallback(cb)
-
-
-    def test_multipart(self):
-        """
-        Test parsing data in multipart format: it should fill the C{files}
-        attribute.
-        """
-        ctype = http_headers.MimeType('multipart', 'form-data',
-                                      (('boundary', '---weeboundary'),))
-        content="""-----weeboundary\r
-Content-Disposition: form-data; name="FileNameOne"; filename="myfilename"\r
-Content-Type: text/html\r
-\r
-my great content wooo\r
------weeboundary--\r
-"""
-        root = resource.Resource()
-        request = SimpleRequest(server.Site(root), "GET", "/",
-                http_headers.Headers({'content-type': ctype}), content)
-        def cb(ign):
-            self.assertEquals(request.args, {})
-            self.assertEquals(request.files.keys(), ['FileNameOne'])
-            self.assertEquals(request.files.values()[0][0][:2],
-                  ('myfilename', http_headers.MimeType('text', 'html', {})))
-            f = request.files.values()[0][0][2]
-            self.assertEquals(f.read(), "my great content wooo")
-        return server.parsePOSTData(request).addCallback(cb)
-
-
-    def test_multipartWithNoBoundary(self):
-        """
-        If the boundary type is not specified, parsing should fail with a
-        C{http.HTTPError}.
-        """
-        ctype = http_headers.MimeType('multipart', 'form-data')
-        content="""-----weeboundary\r
-Content-Disposition: form-data; name="FileNameOne"; filename="myfilename"\r
-Content-Type: text/html\r
-\r
-my great content wooo\r
------weeboundary--\r
-"""
-        root = resource.Resource()
-        request = SimpleRequest(server.Site(root), "GET", "/",
-                http_headers.Headers({'content-type': ctype}), content)
-        return self.assertFailure(server.parsePOSTData(request),
-            http.HTTPError)
-
-
-    def test_wrongContentType(self):
-        """
-        Check that a content-type not handled raise a C{http.HTTPError}.
-        """
-        ctype = http_headers.MimeType('application', 'foobar')
-        content = "key=value&multiple=two+words&multiple=more%20words"
-        root = resource.Resource()
-        request = SimpleRequest(server.Site(root), "GET", "/",
-                http_headers.Headers({'content-type': ctype}), content)
-        return self.assertFailure(server.parsePOSTData(request),
-            http.HTTPError)
-
-
-    def test_mimeParsingError(self):
-        """
-        A malformed content should result in a C{http.HTTPError}.
-        
-        The tested content has an invalid closing boundary.
-        """
-        ctype = http_headers.MimeType('multipart', 'form-data',
-                                      (('boundary', '---weeboundary'),))
-        content="""-----weeboundary\r
-Content-Disposition: form-data; name="FileNameOne"; filename="myfilename"\r
-Content-Type: text/html\r
-\r
-my great content wooo\r
------weeoundary--\r
-"""
-        root = resource.Resource()
-        request = SimpleRequest(server.Site(root), "GET", "/",
-                http_headers.Headers({'content-type': ctype}), content)
-        return self.assertFailure(server.parsePOSTData(request),
-            http.HTTPError)
-
-
-    def test_multipartMaxMem(self):
-        """
-        Check that the C{maxMem} parameter makes the parsing raise an
-        exception if the value is reached.
-        """
-        ctype = http_headers.MimeType('multipart', 'form-data',
-                                      (('boundary', '---weeboundary'),))
-        content="""-----weeboundary\r
-Content-Disposition: form-data; name="FileNameOne"\r
-Content-Type: text/html\r
-\r
-my great content wooo
-and even more and more\r
------weeboundary--\r
-"""
-        root = resource.Resource()
-        request = SimpleRequest(server.Site(root), "GET", "/",
-                http_headers.Headers({'content-type': ctype}), content)
-        def cb(res):
-            self.assertEquals(res.response.description,
-                "Maximum length of 10 bytes exceeded.")
-        return self.assertFailure(server.parsePOSTData(request, maxMem=10),
-            http.HTTPError).addCallback(cb)
-
-
-    def test_multipartMaxSize(self):
-        """
-        Check that the C{maxSize} parameter makes the parsing raise an
-        exception if the data is too big.
-        """
-        ctype = http_headers.MimeType('multipart', 'form-data',
-                                      (('boundary', '---weeboundary'),))
-        content="""-----weeboundary\r
-Content-Disposition: form-data; name="FileNameOne"; filename="myfilename"\r
-Content-Type: text/html\r
-\r
-my great content wooo
-and even more and more\r
------weeboundary--\r
-"""
-        root = resource.Resource()
-        request = SimpleRequest(server.Site(root), "GET", "/",
-                http_headers.Headers({'content-type': ctype}), content)
-        def cb(res):
-            self.assertEquals(res.response.description,
-                "Maximum length of 10 bytes exceeded.")
-        return self.assertFailure(server.parsePOSTData(request, maxSize=10),
-            http.HTTPError).addCallback(cb)
-
-
-    def test_maxFields(self):
-        """
-        Check that the C{maxSize} parameter makes the parsing raise an
-        exception if the data contains too many fields.
-        """
-        ctype = http_headers.MimeType('multipart', 'form-data',
-                                      (('boundary', '---xyz'),))
-        content = """-----xyz\r
-Content-Disposition: form-data; name="foo"\r
-\r
-Foo Bar\r
------xyz\r
-Content-Disposition: form-data; name="foo"\r
-\r
-Baz\r
------xyz\r
-Content-Disposition: form-data; name="file"; filename="filename"\r
-Content-Type: text/html\r
-\r
-blah\r
------xyz\r
-Content-Disposition: form-data; name="file"; filename="filename"\r
-Content-Type: text/plain\r
-\r
-bleh\r
------xyz--\r
-"""
-        root = resource.Resource()
-        request = SimpleRequest(server.Site(root), "GET", "/",
-                http_headers.Headers({'content-type': ctype}), content)
-        def cb(res):
-            self.assertEquals(res.response.description,
-                "Maximum number of fields 3 exceeded")
-        return self.assertFailure(server.parsePOSTData(request, maxFields=3),
-            http.HTTPError).addCallback(cb)
-
-
-    def test_otherErrors(self):
-        """
-        Test that errors durign parsing other than C{MimeFormatError} are
-        propagated.
-        """
-        ctype = http_headers.MimeType('multipart', 'form-data',
-                                      (('boundary', '---weeboundary'),))
-        # XXX: maybe this is not a good example
-        # parseContentDispositionFormData could handle this problem
-        content="""-----weeboundary\r
-Content-Disposition: form-data; name="FileNameOne"; filename="myfilename and invalid data \r
------weeboundary--\r
-"""
-        root = resource.Resource()
-        request = SimpleRequest(server.Site(root), "GET", "/",
-                http_headers.Headers({'content-type': ctype}), content)
-        return self.assertFailure(server.parsePOSTData(request),
-            ValueError)
-

Copied: CalendarServer/trunk/twext/web2/test/test_server.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_server.py)
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_server.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/test/test_server.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,878 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A test harness for the twext.web2 server.
+"""
+
+from zope.interface import implements
+
+from twisted.python import components
+from twext.web2 import http, http_headers, iweb, server
+from twext.web2 import resource, stream, compat
+from twisted.trial import unittest
+from twisted.internet import reactor, defer, address
+
+
+
+class NotResource(object):
+    """
+    Class which does not implement IResource.
+
+    Used as an adaptee by L{AdaptionTestCase.test_registered} to test that
+    if an object which does not provide IResource is adapted to IResource
+    and there is an adapter to IResource registered, that adapter is used.
+    """
+
+
+
+class ResourceAdapter(object):
+    """
+    Adapter to IResource.
+
+    Registered as an adapter from NotResource to IResource so that
+    L{AdaptionTestCase.test_registered} can test that such an adapter will
+    be used.
+    """
+    implements(iweb.IResource)
+
+    def __init__(self, original):
+        pass
+
+components.registerAdapter(ResourceAdapter, NotResource, iweb.IResource)
+
+
+
+class NotOldResource(object):
+    """
+    Class which does not implement IOldNevowResource or IResource.
+
+    Used as an adaptee by L{AdaptionTestCase.test_transitive} to test that
+    if an object which does not provide IResource or IOldNevowResource is
+    adapted to IResource and there is an adapter to IOldNevowResource
+    registered, first that adapter is used, then the included adapter from
+    IOldNevowResource to IResource is used.
+    """
+
+
+
+class OldResourceAdapter(object):
+    """
+    Adapter to IOldNevowResource.
+
+    Registered as an adapter from NotOldResource to IOldNevowResource so
+    that L{AdaptionTestCase.test_transitive} can test that such an adapter
+    will be used to allow the initial input to be adapted to IResource.
+    """
+    implements(iweb.IOldNevowResource)
+
+    def __init__(self, original):
+        pass
+
+components.registerAdapter(OldResourceAdapter, NotOldResource, iweb.IOldNevowResource)
+
+
+
+class AdaptionTestCase(unittest.TestCase):
+    """
+    Test the adaption of various objects to IResource.
+
+    Necessary due to the special implementation of __call__ on IResource
+    which extends the behavior provided by the base Interface.__call__.
+    """
+    def test_unadaptable(self):
+        """
+        Test that attempting to adapt to IResource an object not adaptable
+        to IResource raises an exception or returns the specified alternate
+        object.
+        """
+        class Unadaptable(object):
+            pass
+        self.assertRaises(TypeError, iweb.IResource, Unadaptable())
+        alternate = object()
+        self.assertIdentical(iweb.IResource(Unadaptable(), alternate), alternate)
+
+
+    def test_redundant(self):
+        """
+        Test that the adaption to IResource of an object which provides
+        IResource returns the same object.
+        """
+        class Resource(object):
+            implements(iweb.IResource)
+        resource = Resource()
+        self.assertIdentical(iweb.IResource(resource), resource)
+
+
+    def test_registered(self):
+        """
+        Test that if an adapter exists which can provide IResource for an
+        object which does not provide it, that adapter is used.
+        """
+        notResource = NotResource()
+        self.failUnless(isinstance(iweb.IResource(notResource), ResourceAdapter))
+
+
+    def test_oldResources(self):
+        """
+        Test that providers of L{IOldNevowResource} can be adapted to
+        IResource automatically.
+        """
+        class OldResource(object):
+            implements(iweb.IOldNevowResource)
+        oldResource = OldResource()
+        resource = iweb.IResource(oldResource)
+        self.failUnless(isinstance(resource, compat.OldNevowResourceAdapter))
+
+
+    def test_transitive(self):
+        """
+        Test that a special-case transitive adaption from something to
+        IOldNevowResource to IResource is possible.
+        """
+        notResource = NotOldResource()
+        resource = iweb.IResource(notResource)
+        self.failUnless(isinstance(resource, compat.OldNevowResourceAdapter))
+
+
+
+class SimpleRequest(server.Request):
+    """I can be used in cases where a Request object is necessary
+    but it is benificial to bypass the chanRequest
+    """
+
+    clientproto = (1,1)
+
+    def __init__(self, site, method, uri, headers=None, content=None):
+        if not headers:
+            headers = http_headers.Headers(headers)
+
+        super(SimpleRequest, self).__init__(
+            site=site,
+            chanRequest=None,
+            command=method,
+            path=uri,
+            version=self.clientproto,
+            contentLength=len(content or ''),
+            headers=headers)
+
+        self.stream = stream.MemoryStream(content or '')
+
+        self.remoteAddr = address.IPv4Address('TCP', '127.0.0.1', 0)
+        self._parseURL()
+        self.host = 'localhost'
+        self.port = 8080
+
+    def writeResponse(self, response):
+        return response
+
+
+class TestChanRequest:
+    implements(iweb.IChanRequest)
+
+    hostInfo = address.IPv4Address('TCP', 'host', 80), False
+    remoteHost = address.IPv4Address('TCP', 'remotehost', 34567)
+
+
+    def __init__(self, site, method, prepath, uri, length=None,
+                 headers=None, version=(1,1), content=None):
+        self.site = site
+        self.method = method
+        self.prepath = prepath
+        self.uri = uri
+        if headers is None:
+            headers = http_headers.Headers()
+        self.headers = headers
+        self.http_version = version
+        # Anything below here we do not pass as arguments
+        self.request = server.Request(self,
+                                      self.method,
+                                      self.uri,
+                                      self.http_version,
+                                      length,
+                                      self.headers,
+                                      site=self.site,
+                                      prepathuri=self.prepath)
+
+        if content is not None:
+            self.request.handleContentChunk(content)
+            self.request.handleContentComplete()
+
+        self.code = None
+        self.responseHeaders = None
+        self.data = ''
+        self.deferredFinish = defer.Deferred()
+
+    def writeIntermediateResponse(code, headers=None):
+        pass
+
+    def writeHeaders(self, code, headers):
+        self.responseHeaders = headers
+        self.code = code
+
+    def write(self, data):
+        self.data += data
+
+    def finish(self, failed=False):
+        result = self.code, self.responseHeaders, self.data, failed
+        self.finished = True
+        self.deferredFinish.callback(result)
+
+    def abortConnection(self):
+        self.finish(failed=True)
+
+    def registerProducer(self, producer, streaming):
+        pass
+
+    def unregisterProducer(self):
+        pass
+
+    def getHostInfo(self):
+        return self.hostInfo
+
+    def getRemoteHost(self):
+        return self.remoteHost
+
+
+class BaseTestResource(resource.Resource):
+    responseCode = 200
+    responseText = 'This is a fake resource.'
+    responseHeaders = {}
+    addSlash = False
+
+    def __init__(self, children=[]):
+        """
+        @type children: C{list} of C{tuple}
+        @param children: a list of ('path', resource) tuples
+        """
+        for i in children:
+            self.putChild(i[0], i[1])
+
+    def render(self, req):
+        return http.Response(self.responseCode, headers=self.responseHeaders,
+                             stream=self.responseStream())
+
+    def responseStream(self):
+        return stream.MemoryStream(self.responseText)
+
+
+
+_unset = object()
+class BaseCase(unittest.TestCase):
+    """
+    Base class for test cases that involve testing the result
+    of arbitrary HTTP(S) queries.
+    """
+
+    method = 'GET'
+    version = (1, 1)
+    wait_timeout = 5.0
+
+    def chanrequest(self, root, uri, length, headers, method, version, prepath, content):
+        site = server.Site(root)
+        return TestChanRequest(site, method, prepath, uri, length, headers, version, content)
+
+    def getResponseFor(self, root, uri, headers={},
+                       method=None, version=None, prepath='', content=None, length=_unset):
+        if not isinstance(headers, http_headers.Headers):
+            headers = http_headers.Headers(headers)
+        if length is _unset:
+            if content is not None:
+                length = len(content)
+            else:
+                length = 0
+
+        if method is None:
+            method = self.method
+        if version is None:
+            version = self.version
+
+        cr = self.chanrequest(root, uri, length, headers, method, version, prepath, content)
+        cr.request.process()
+        return cr.deferredFinish
+
+    def assertResponse(self, request_data, expected_response, failure=False):
+        """
+        @type request_data: C{tuple}
+        @type expected_response: C{tuple}
+        @param request_data: A tuple of arguments to pass to L{getResponseFor}:
+                             (root, uri, headers, method, version, prepath).
+                             Root resource and requested URI are required,
+                             and everything else is optional.
+        @param expected_response: A 3-tuple of the expected response:
+                                  (responseCode, headers, htmlData)
+        """
+        d = self.getResponseFor(*request_data)
+        d.addCallback(self._cbGotResponse, expected_response, failure)
+
+        return d
+
+    def _cbGotResponse(self, (code, headers, data, failed), expected_response, expectedfailure=False):
+        expected_code, expected_headers, expected_data = expected_response
+        self.assertEquals(code, expected_code)
+        if expected_data is not None:
+            self.assertEquals(data, expected_data)
+        for key, value in expected_headers.iteritems():
+            self.assertEquals(headers.getHeader(key), value)
+        self.assertEquals(failed, expectedfailure)
+
+
+
+class SampleWebTest(BaseCase):
+    class SampleTestResource(BaseTestResource):
+        addSlash = True
+        def child_validChild(self, req):
+            f = BaseTestResource()
+            f.responseCode = 200
+            f.responseText = 'This is a valid child resource.'
+            return f
+
+        def child_missingChild(self, req):
+            f = BaseTestResource()
+            f.responseCode = 404
+            f.responseStream = lambda self: None
+            return f
+
+        def child_remoteAddr(self, req):
+            f = BaseTestResource()
+            f.responseCode = 200
+            f.responseText = 'Remote Addr: %r' % req.remoteAddr.host
+            return f
+
+    def setUp(self):
+        self.root = self.SampleTestResource()
+
+    def test_root(self):
+        return self.assertResponse(
+            (self.root, 'http://host/'),
+            (200, {}, 'This is a fake resource.'))
+
+    def test_validChild(self):
+        return self.assertResponse(
+            (self.root, 'http://host/validChild'),
+            (200, {}, 'This is a valid child resource.'))
+
+    def test_invalidChild(self):
+        return self.assertResponse(
+            (self.root, 'http://host/invalidChild'),
+            (404, {}, None))
+
+    def test_remoteAddrExposure(self):
+        return self.assertResponse(
+            (self.root, 'http://host/remoteAddr'),
+            (200, {}, "Remote Addr: 'remotehost'"))
+
+    def test_leafresource(self):
+        class TestResource(resource.LeafResource):
+            def render(self, req):
+                return http.Response(stream="prepath:%s postpath:%s" % (
+                        req.prepath,
+                        req.postpath))
+
+        return self.assertResponse(
+            (TestResource(), 'http://host/consumed/path/segments'),
+            (200, {}, "prepath:[] postpath:['consumed', 'path', 'segments']"))
+
+    def test_redirectResource(self):
+        redirectResource = resource.RedirectResource(scheme='https',
+                                                     host='localhost',
+                                                     port=443,
+                                                     path='/foo',
+                                                     querystring='bar=baz')
+
+        return self.assertResponse(
+            (redirectResource, 'http://localhost/'),
+            (301, {'location': 'https://localhost/foo?bar=baz'}, None))
+
+
+class URLParsingTest(BaseCase):
+    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 test_normal(self):
+        return self.assertResponse(
+            (self.root, '/path', {'Host':'host'}),
+            (200, {}, 'Host:host, Path:/path'))
+
+    def test_fullurl(self):
+        return self.assertResponse(
+            (self.root, 'http://host/path'),
+            (200, {}, 'Host:host, Path:/path'))
+
+    def test_strangepath(self):
+        # Ensure that the double slashes don't confuse it
+        return self.assertResponse(
+            (self.root, '//path', {'Host':'host'}),
+            (200, {}, 'Host:host, Path://path'))
+
+    def test_strangepathfull(self):
+        return self.assertResponse(
+            (self.root, 'http://host//path'),
+            (200, {}, 'Host:host, Path://path'))
+
+
+
+class TestDeferredRendering(BaseCase):
+    class ResourceWithDeferreds(BaseTestResource):
+        addSlash=True
+        responseText = 'I should be wrapped in a Deferred.'
+        def render(self, req):
+            d = defer.Deferred()
+            reactor.callLater(
+                0, d.callback, BaseTestResource.render(self, req))
+            return d
+
+        def child_deferred(self, req):
+            d = defer.Deferred()
+            reactor.callLater(0, d.callback, BaseTestResource())
+            return d
+
+    def test_deferredRootResource(self):
+        return self.assertResponse(
+            (self.ResourceWithDeferreds(), 'http://host/'),
+            (200, {}, 'I should be wrapped in a Deferred.'))
+
+    def test_deferredChild(self):
+        return self.assertResponse(
+            (self.ResourceWithDeferreds(), 'http://host/deferred'),
+            (200, {}, 'This is a fake resource.'))
+
+
+
+class RedirectResourceTest(BaseCase):
+    def html(url):
+        return "<html><head><title>Moved Permanently</title></head><body><h1>Moved Permanently</h1><p>Document moved to %s.</p></body></html>" % (url,)
+    html = staticmethod(html)
+
+    def test_noRedirect(self):
+        # This is useless, since it's a loop, but hey
+        ds = []
+        for url in ("http://host/", "http://host/foo"):
+            ds.append(self.assertResponse(
+                (resource.RedirectResource(), url),
+                (301, {"location": url}, self.html(url))
+            ))
+        return defer.DeferredList(ds, fireOnOneErrback=True)
+
+    def test_hostRedirect(self):
+        ds = []
+        for url1, url2 in (
+            ("http://host/", "http://other/"),
+            ("http://host/foo", "http://other/foo"),
+        ):
+            ds.append(self.assertResponse(
+                (resource.RedirectResource(host="other"), url1),
+                (301, {"location": url2}, self.html(url2))
+            ))
+        return defer.DeferredList(ds, fireOnOneErrback=True)
+
+    def test_pathRedirect(self):
+        root = BaseTestResource()
+        redirect = resource.RedirectResource(path="/other")
+        root.putChild("r", redirect)
+
+        ds = []
+        for url1, url2 in (
+            ("http://host/r", "http://host/other"),
+            ("http://host/r/foo", "http://host/other"),
+        ):
+            ds.append(self.assertResponse(
+                (resource.RedirectResource(path="/other"), url1),
+                (301, {"location": url2}, self.html(url2))
+            ))
+        return defer.DeferredList(ds, fireOnOneErrback=True)
+
+
+
+class EmptyResource(resource.Resource):
+    def __init__(self, test):
+        self.test = test
+
+    def render(self, request):
+        self.test.assertEquals(request.urlForResource(self), self.expectedURI)
+        return 201
+
+
+
+class RememberURIs(BaseCase):
+    """
+    Tests for URI memory and lookup mechanism in server.Request.
+    """
+    def test_requestedResource(self):
+        """
+        Test urlForResource() on deeply nested resource looked up via
+        request processing.
+        """
+        root = EmptyResource(self)
+        root.expectedURI = "/"
+
+        foo = EmptyResource(self)
+        foo.expectedURI = "/foo"
+        root.putChild("foo", foo)
+
+        bar = EmptyResource(self)
+        bar.expectedURI = foo.expectedURI + "/bar"
+        foo.putChild("bar", bar)
+
+        baz = EmptyResource(self)
+        baz.expectedURI = bar.expectedURI + "/baz"
+        bar.putChild("baz", baz)
+
+        ds = []
+
+        for uri in (foo.expectedURI, bar.expectedURI, baz.expectedURI):
+            ds.append(self.assertResponse(
+                (root, uri, {'Host':'host'}),
+                (201, {}, None),
+            ))
+
+        return defer.DeferredList(ds, fireOnOneErrback=True)
+
+    def test_urlEncoding(self):
+        """
+        Test to make sure that URL encoding is working.
+        """
+        root = EmptyResource(self)
+        root.expectedURI = "/"
+
+        child = EmptyResource(self)
+        child.expectedURI = "/foo%20bar"
+
+        root.putChild("foo bar", child)
+
+        return self.assertResponse(
+            (root, child.expectedURI, {'Host':'host'}),
+            (201, {}, None)
+        )
+
+    def test_locateResource(self):
+        """
+        Test urlForResource() on resource looked up via a locateResource() call.
+        """
+        root = resource.Resource()
+        child = resource.Resource()
+        root.putChild("foo", child)
+
+        request = SimpleRequest(server.Site(root), "GET", "/")
+
+        def gotResource(resource):
+            self.assertEquals("/foo", request.urlForResource(resource))
+
+        d = defer.maybeDeferred(request.locateResource, "/foo")
+        d.addCallback(gotResource)
+        return d
+
+    def test_unknownResource(self):
+        """
+        Test urlForResource() on unknown resource.
+        """
+        root = resource.Resource()
+        child = resource.Resource()
+        request = SimpleRequest(server.Site(root), "GET", "/")
+
+        self.assertRaises(server.NoURLForResourceError, request.urlForResource, child)
+
+    def test_locateChildResource(self):
+        """
+        Test urlForResource() on deeply nested resource looked up via
+        locateChildResource().
+        """
+        root = EmptyResource(self)
+        root.expectedURI = "/"
+
+        foo = EmptyResource(self)
+        foo.expectedURI = "/foo"
+        root.putChild("foo", foo)
+
+        bar = EmptyResource(self)
+        bar.expectedURI = "/foo/bar"
+        foo.putChild("bar", bar)
+
+        baz = EmptyResource(self)
+        baz.expectedURI = "/foo/bar/b%20a%20z"
+        bar.putChild("b a z", baz)
+
+        request = SimpleRequest(server.Site(root), "GET", "/")
+
+        def gotResource(resource):
+            # Make sure locateChildResource() gave us the right answer
+            self.assertEquals(resource, bar)
+
+            return request.locateChildResource(resource, "b a z").addCallback(gotChildResource)
+
+        def gotChildResource(resource):
+            # Make sure locateChildResource() gave us the right answer
+            self.assertEquals(resource, baz)
+
+            self.assertEquals(resource.expectedURI, request.urlForResource(resource))
+
+        d = request.locateResource(bar.expectedURI)
+        d.addCallback(gotResource)
+        return d
+
+    def test_deferredLocateChild(self):
+        """
+        Test deferred value from locateChild()
+        """
+        class DeferredLocateChild(resource.Resource):
+            def locateChild(self, req, segments):
+                return defer.maybeDeferred(
+                    super(DeferredLocateChild, self).locateChild,
+                    req, segments
+                )
+
+        root = DeferredLocateChild()
+        child = resource.Resource()
+        root.putChild("foo", child)
+
+        request = SimpleRequest(server.Site(root), "GET", "/foo")
+
+        def gotResource(resource):
+            self.assertEquals("/foo", request.urlForResource(resource))
+
+        d = request.locateResource("/foo")
+        d.addCallback(gotResource)
+        return d
+
+
+
+class ParsePostDataTests(unittest.TestCase):
+    """
+    Tests for L{server.parsePOSTData}.
+    """
+
+    def test_noData(self):
+        """
+        Parsing a request without data should succeed but should not fill the
+        C{args} and C{files} attributes of the request.
+        """
+        root = resource.Resource()
+        request = SimpleRequest(server.Site(root), "GET", "/")
+        def cb(ign):
+            self.assertEquals(request.args, {})
+            self.assertEquals(request.files, {})
+        return server.parsePOSTData(request).addCallback(cb)
+
+
+    def test_noContentType(self):
+        """
+        Parsing a request without content-type should succeed but should not
+        fill the C{args} and C{files} attributes of the request.
+        """
+        root = resource.Resource()
+        request = SimpleRequest(server.Site(root), "GET", "/", content="foo")
+        def cb(ign):
+            self.assertEquals(request.args, {})
+            self.assertEquals(request.files, {})
+        return server.parsePOSTData(request).addCallback(cb)
+
+
+    def test_urlencoded(self):
+        """
+        Test parsing data in urlencoded format: it should end in the C{args}
+        attribute.
+        """
+        ctype = http_headers.MimeType('application', 'x-www-form-urlencoded')
+        content = "key=value&multiple=two+words&multiple=more%20words"
+        root = resource.Resource()
+        request = SimpleRequest(server.Site(root), "GET", "/",
+                http_headers.Headers({'content-type': ctype}), content)
+        def cb(ign):
+            self.assertEquals(request.files, {})
+            self.assertEquals(request.args,
+                {'multiple': ['two words', 'more words'], 'key': ['value']})
+        return server.parsePOSTData(request).addCallback(cb)
+
+
+    def test_multipart(self):
+        """
+        Test parsing data in multipart format: it should fill the C{files}
+        attribute.
+        """
+        ctype = http_headers.MimeType('multipart', 'form-data',
+                                      (('boundary', '---weeboundary'),))
+        content="""-----weeboundary\r
+Content-Disposition: form-data; name="FileNameOne"; filename="myfilename"\r
+Content-Type: text/html\r
+\r
+my great content wooo\r
+-----weeboundary--\r
+"""
+        root = resource.Resource()
+        request = SimpleRequest(server.Site(root), "GET", "/",
+                http_headers.Headers({'content-type': ctype}), content)
+        def cb(ign):
+            self.assertEquals(request.args, {})
+            self.assertEquals(request.files.keys(), ['FileNameOne'])
+            self.assertEquals(request.files.values()[0][0][:2],
+                  ('myfilename', http_headers.MimeType('text', 'html', {})))
+            f = request.files.values()[0][0][2]
+            self.assertEquals(f.read(), "my great content wooo")
+        return server.parsePOSTData(request).addCallback(cb)
+
+
+    def test_multipartWithNoBoundary(self):
+        """
+        If the boundary type is not specified, parsing should fail with a
+        C{http.HTTPError}.
+        """
+        ctype = http_headers.MimeType('multipart', 'form-data')
+        content="""-----weeboundary\r
+Content-Disposition: form-data; name="FileNameOne"; filename="myfilename"\r
+Content-Type: text/html\r
+\r
+my great content wooo\r
+-----weeboundary--\r
+"""
+        root = resource.Resource()
+        request = SimpleRequest(server.Site(root), "GET", "/",
+                http_headers.Headers({'content-type': ctype}), content)
+        return self.assertFailure(server.parsePOSTData(request),
+            http.HTTPError)
+
+
+    def test_wrongContentType(self):
+        """
+        Check that a content-type not handled raise a C{http.HTTPError}.
+        """
+        ctype = http_headers.MimeType('application', 'foobar')
+        content = "key=value&multiple=two+words&multiple=more%20words"
+        root = resource.Resource()
+        request = SimpleRequest(server.Site(root), "GET", "/",
+                http_headers.Headers({'content-type': ctype}), content)
+        return self.assertFailure(server.parsePOSTData(request),
+            http.HTTPError)
+
+
+    def test_mimeParsingError(self):
+        """
+        A malformed content should result in a C{http.HTTPError}.
+        
+        The tested content has an invalid closing boundary.
+        """
+        ctype = http_headers.MimeType('multipart', 'form-data',
+                                      (('boundary', '---weeboundary'),))
+        content="""-----weeboundary\r
+Content-Disposition: form-data; name="FileNameOne"; filename="myfilename"\r
+Content-Type: text/html\r
+\r
+my great content wooo\r
+-----weeoundary--\r
+"""
+        root = resource.Resource()
+        request = SimpleRequest(server.Site(root), "GET", "/",
+                http_headers.Headers({'content-type': ctype}), content)
+        return self.assertFailure(server.parsePOSTData(request),
+            http.HTTPError)
+
+
+    def test_multipartMaxMem(self):
+        """
+        Check that the C{maxMem} parameter makes the parsing raise an
+        exception if the value is reached.
+        """
+        ctype = http_headers.MimeType('multipart', 'form-data',
+                                      (('boundary', '---weeboundary'),))
+        content="""-----weeboundary\r
+Content-Disposition: form-data; name="FileNameOne"\r
+Content-Type: text/html\r
+\r
+my great content wooo
+and even more and more\r
+-----weeboundary--\r
+"""
+        root = resource.Resource()
+        request = SimpleRequest(server.Site(root), "GET", "/",
+                http_headers.Headers({'content-type': ctype}), content)
+        def cb(res):
+            self.assertEquals(res.response.description,
+                "Maximum length of 10 bytes exceeded.")
+        return self.assertFailure(server.parsePOSTData(request, maxMem=10),
+            http.HTTPError).addCallback(cb)
+
+
+    def test_multipartMaxSize(self):
+        """
+        Check that the C{maxSize} parameter makes the parsing raise an
+        exception if the data is too big.
+        """
+        ctype = http_headers.MimeType('multipart', 'form-data',
+                                      (('boundary', '---weeboundary'),))
+        content="""-----weeboundary\r
+Content-Disposition: form-data; name="FileNameOne"; filename="myfilename"\r
+Content-Type: text/html\r
+\r
+my great content wooo
+and even more and more\r
+-----weeboundary--\r
+"""
+        root = resource.Resource()
+        request = SimpleRequest(server.Site(root), "GET", "/",
+                http_headers.Headers({'content-type': ctype}), content)
+        def cb(res):
+            self.assertEquals(res.response.description,
+                "Maximum length of 10 bytes exceeded.")
+        return self.assertFailure(server.parsePOSTData(request, maxSize=10),
+            http.HTTPError).addCallback(cb)
+
+
+    def test_maxFields(self):
+        """
+        Check that the C{maxSize} parameter makes the parsing raise an
+        exception if the data contains too many fields.
+        """
+        ctype = http_headers.MimeType('multipart', 'form-data',
+                                      (('boundary', '---xyz'),))
+        content = """-----xyz\r
+Content-Disposition: form-data; name="foo"\r
+\r
+Foo Bar\r
+-----xyz\r
+Content-Disposition: form-data; name="foo"\r
+\r
+Baz\r
+-----xyz\r
+Content-Disposition: form-data; name="file"; filename="filename"\r
+Content-Type: text/html\r
+\r
+blah\r
+-----xyz\r
+Content-Disposition: form-data; name="file"; filename="filename"\r
+Content-Type: text/plain\r
+\r
+bleh\r
+-----xyz--\r
+"""
+        root = resource.Resource()
+        request = SimpleRequest(server.Site(root), "GET", "/",
+                http_headers.Headers({'content-type': ctype}), content)
+        def cb(res):
+            self.assertEquals(res.response.description,
+                "Maximum number of fields 3 exceeded")
+        return self.assertFailure(server.parsePOSTData(request, maxFields=3),
+            http.HTTPError).addCallback(cb)
+
+
+    def test_otherErrors(self):
+        """
+        Test that errors durign parsing other than C{MimeFormatError} are
+        propagated.
+        """
+        ctype = http_headers.MimeType('multipart', 'form-data',
+                                      (('boundary', '---weeboundary'),))
+        # XXX: maybe this is not a good example
+        # parseContentDispositionFormData could handle this problem
+        content="""-----weeboundary\r
+Content-Disposition: form-data; name="FileNameOne"; filename="myfilename and invalid data \r
+-----weeboundary--\r
+"""
+        root = resource.Resource()
+        request = SimpleRequest(server.Site(root), "GET", "/",
+                http_headers.Headers({'content-type': ctype}), content)
+        return self.assertFailure(server.parsePOSTData(request),
+            ValueError)
+

Deleted: CalendarServer/trunk/twext/web2/test/test_static.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_static.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/test/test_static.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,146 +0,0 @@
-# Copyright (c) 2008 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Tests for L{twext.web2.static}.
-"""
-
-import os
-
-from twext.web2.test.test_server import BaseCase
-from twext.web2 import static
-from twext.web2 import http_headers
-from twext.web2 import stream
-from twext.web2 import iweb
-
-
-
-class TestData(BaseCase):
-    def setUp(self):
-        self.text = "Hello, World\n"
-        self.data = static.Data(self.text, "text/plain")
-
-    def test_dataState(self):
-        """
-        Test the internal state of the Data object
-        """
-        self.assert_(hasattr(self.data, "created_time"))
-        self.assertEquals(self.data.data, self.text)
-        self.assertEquals(self.data.type, http_headers.MimeType("text", "plain"))
-        self.assertEquals(self.data.contentType(), http_headers.MimeType("text", "plain"))
-
-
-    def test_etag(self):
-        """
-        Test that we can get an ETag
-        """
-        self.failUnless(self.data.etag())
-
-
-    def test_render(self):
-        """
-        Test that the result from Data.render is acceptable, including the
-        response code, the content-type header, and the actual response body
-        itself.
-        """
-        response = iweb.IResponse(self.data.render(None))
-        self.assertEqual(response.code, 200)
-        self.assert_(response.headers.hasHeader("content-type"))
-        self.assertEqual(response.headers.getHeader("content-type"),
-                         http_headers.MimeType("text", "plain"))
-        def checkStream(data):
-            self.assertEquals(str(data), self.text)
-        return stream.readStream(iweb.IResponse(self.data.render(None)).stream,
-                                 checkStream)
-
-
-
-class TestFileSaver(BaseCase):
-    def setUp(self):
-        """
-        Create an empty directory and a resource which will save uploads to
-        that directory.
-        """
-        self.tempdir = self.mktemp()
-        os.mkdir(self.tempdir)
-
-        self.root = static.FileSaver(self.tempdir,
-                              expectedFields=['FileNameOne'],
-                              maxBytes=16)
-        self.root.addSlash = True
-
-    def uploadFile(self, fieldname, filename, mimetype, content, resrc=None,
-                   host='foo', path='/'):
-        if not resrc:
-            resrc = self.root
-
-        ctype = http_headers.MimeType('multipart', 'form-data',
-                                      (('boundary', '---weeboundary'),))
-
-        return self.getResponseFor(resrc, '/',
-                            headers={'host': 'foo',
-                                     'content-type': ctype },
-                            length=len(content),
-                            method='POST',
-                            content="""-----weeboundary\r
-Content-Disposition: form-data; name="%s"; filename="%s"\r
-Content-Type: %s\r
-\r
-%s\r
------weeboundary--\r
-""" % (fieldname, filename, mimetype, content))
-
-    def _CbAssertInResponse(self, (code, headers, data, failed),
-                            expected_response, expectedFailure=False):
-
-        expected_code, expected_headers, expected_data = expected_response
-        self.assertEquals(code, expected_code)
-
-        if expected_data is not None:
-            self.failUnlessSubstring(expected_data, data)
-
-        for key, value in expected_headers.iteritems():
-            self.assertEquals(headers.getHeader(key), value)
-
-        self.assertEquals(failed, expectedFailure)
-
-    def fileNameFromResponse(self, response):
-        (code, headers, data, failure) = response
-        return data[data.index('Saved file')+11:data.index('<br />')]
-
-    def assertInResponse(self, response, expected_response, failure=False):
-        d = response
-        d.addCallback(self._CbAssertInResponse, expected_response, failure)
-        return d
-
-    def test_enforcesMaxBytes(self):
-        return self.assertInResponse(
-            self.uploadFile('FileNameOne', 'myfilename', 'text/html', 'X'*32),
-            (200, {}, 'exceeds maximum length'))
-
-    def test_enforcesMimeType(self):
-        return self.assertInResponse(
-            self.uploadFile('FileNameOne', 'myfilename',
-                            'application/x-python', 'X'),
-            (200, {}, 'type not allowed'))
-
-    def test_invalidField(self):
-        return self.assertInResponse(
-            self.uploadFile('NotARealField', 'myfilename', 'text/html', 'X'),
-            (200, {}, 'not a valid field'))
-
-    def test_reportFileSave(self):
-        return self.assertInResponse(
-            self.uploadFile('FileNameOne', 'myfilename', 'text/plain', 'X'),
-            (200, {}, 'Saved file'))
-
-    def test_compareFileContents(self):
-        def gotFname(fname):
-            contents = file(fname, 'rb').read()
-            self.assertEquals(contents, 'Test contents\n')
-
-        d = self.uploadFile('FileNameOne', 'myfilename', 'text/plain',
-                            'Test contents\n')
-        d.addCallback(self.fileNameFromResponse)
-        d.addCallback(gotFname)
-        return d

Copied: CalendarServer/trunk/twext/web2/test/test_static.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_static.py)
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_static.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/test/test_static.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,146 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twext.web2.static}.
+"""
+
+import os
+
+from twext.web2.test.test_server import BaseCase
+from twext.web2 import static
+from twext.web2 import http_headers
+from twext.web2 import stream
+from twext.web2 import iweb
+
+
+
+class TestData(BaseCase):
+    def setUp(self):
+        self.text = "Hello, World\n"
+        self.data = static.Data(self.text, "text/plain")
+
+    def test_dataState(self):
+        """
+        Test the internal state of the Data object
+        """
+        self.assert_(hasattr(self.data, "created_time"))
+        self.assertEquals(self.data.data, self.text)
+        self.assertEquals(self.data.type, http_headers.MimeType("text", "plain"))
+        self.assertEquals(self.data.contentType(), http_headers.MimeType("text", "plain"))
+
+
+    def test_etag(self):
+        """
+        Test that we can get an ETag
+        """
+        self.failUnless(self.data.etag())
+
+
+    def test_render(self):
+        """
+        Test that the result from Data.render is acceptable, including the
+        response code, the content-type header, and the actual response body
+        itself.
+        """
+        response = iweb.IResponse(self.data.render(None))
+        self.assertEqual(response.code, 200)
+        self.assert_(response.headers.hasHeader("content-type"))
+        self.assertEqual(response.headers.getHeader("content-type"),
+                         http_headers.MimeType("text", "plain"))
+        def checkStream(data):
+            self.assertEquals(str(data), self.text)
+        return stream.readStream(iweb.IResponse(self.data.render(None)).stream,
+                                 checkStream)
+
+
+
+class TestFileSaver(BaseCase):
+    def setUp(self):
+        """
+        Create an empty directory and a resource which will save uploads to
+        that directory.
+        """
+        self.tempdir = self.mktemp()
+        os.mkdir(self.tempdir)
+
+        self.root = static.FileSaver(self.tempdir,
+                              expectedFields=['FileNameOne'],
+                              maxBytes=16)
+        self.root.addSlash = True
+
+    def uploadFile(self, fieldname, filename, mimetype, content, resrc=None,
+                   host='foo', path='/'):
+        if not resrc:
+            resrc = self.root
+
+        ctype = http_headers.MimeType('multipart', 'form-data',
+                                      (('boundary', '---weeboundary'),))
+
+        return self.getResponseFor(resrc, '/',
+                            headers={'host': 'foo',
+                                     'content-type': ctype },
+                            length=len(content),
+                            method='POST',
+                            content="""-----weeboundary\r
+Content-Disposition: form-data; name="%s"; filename="%s"\r
+Content-Type: %s\r
+\r
+%s\r
+-----weeboundary--\r
+""" % (fieldname, filename, mimetype, content))
+
+    def _CbAssertInResponse(self, (code, headers, data, failed),
+                            expected_response, expectedFailure=False):
+
+        expected_code, expected_headers, expected_data = expected_response
+        self.assertEquals(code, expected_code)
+
+        if expected_data is not None:
+            self.failUnlessSubstring(expected_data, data)
+
+        for key, value in expected_headers.iteritems():
+            self.assertEquals(headers.getHeader(key), value)
+
+        self.assertEquals(failed, expectedFailure)
+
+    def fileNameFromResponse(self, response):
+        (code, headers, data, failure) = response
+        return data[data.index('Saved file')+11:data.index('<br />')]
+
+    def assertInResponse(self, response, expected_response, failure=False):
+        d = response
+        d.addCallback(self._CbAssertInResponse, expected_response, failure)
+        return d
+
+    def test_enforcesMaxBytes(self):
+        return self.assertInResponse(
+            self.uploadFile('FileNameOne', 'myfilename', 'text/html', 'X'*32),
+            (200, {}, 'exceeds maximum length'))
+
+    def test_enforcesMimeType(self):
+        return self.assertInResponse(
+            self.uploadFile('FileNameOne', 'myfilename',
+                            'application/x-python', 'X'),
+            (200, {}, 'type not allowed'))
+
+    def test_invalidField(self):
+        return self.assertInResponse(
+            self.uploadFile('NotARealField', 'myfilename', 'text/html', 'X'),
+            (200, {}, 'not a valid field'))
+
+    def test_reportFileSave(self):
+        return self.assertInResponse(
+            self.uploadFile('FileNameOne', 'myfilename', 'text/plain', 'X'),
+            (200, {}, 'Saved file'))
+
+    def test_compareFileContents(self):
+        def gotFname(fname):
+            contents = file(fname, 'rb').read()
+            self.assertEquals(contents, 'Test contents\n')
+
+        d = self.uploadFile('FileNameOne', 'myfilename', 'text/plain',
+                            'Test contents\n')
+        d.addCallback(self.fileNameFromResponse)
+        d.addCallback(gotFname)
+        return d

Deleted: CalendarServer/trunk/twext/web2/test/test_stream.py
===================================================================
--- CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_stream.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twext/web2/test/test_stream.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,622 +0,0 @@
-# Copyright (c) 2008 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Tests for the stream implementations in L{twext.web2}.
-"""
-
-import tempfile, sys, os
-
-from zope.interface import implements
-
-# sibpath is *not* unused - the doctests use it.
-from twisted.python.util import sibpath
-from twisted.internet import reactor, defer, interfaces
-from twisted.trial import unittest
-from twext.web2 import stream
-
-
-def bufstr(data):
-    try:
-        return str(buffer(data))
-    except TypeError:
-        raise TypeError("%s doesn't conform to the buffer interface" % (data,))
-
-
-class SimpleStreamTests:
-    text = '1234567890'
-    def test_split(self):
-        for point in range(10):
-            s = self.makeStream(0)
-            a,b = s.split(point)
-            if point > 0:
-                self.assertEquals(bufstr(a.read()), self.text[:point])
-            self.assertEquals(a.read(), None)
-            if point < len(self.text):
-                self.assertEquals(bufstr(b.read()), self.text[point:])
-            self.assertEquals(b.read(), None)
-
-        for point in range(7):
-            s = self.makeStream(2, 6)
-            self.assertEquals(s.length, 6)
-            a,b = s.split(point)
-            if point > 0:
-                self.assertEquals(bufstr(a.read()), self.text[2:point+2])
-            self.assertEquals(a.read(), None)
-            if point < 6:
-                self.assertEquals(bufstr(b.read()), self.text[point+2:8])
-            self.assertEquals(b.read(), None)
-
-    def test_read(self):
-        s = self.makeStream()
-        self.assertEquals(s.length, len(self.text))
-        self.assertEquals(bufstr(s.read()), self.text)
-        self.assertEquals(s.read(), None)
-
-        s = self.makeStream(0, 4)
-        self.assertEquals(s.length, 4)
-        self.assertEquals(bufstr(s.read()), self.text[0:4])
-        self.assertEquals(s.read(), None)
-        self.assertEquals(s.length, 0)
-
-        s = self.makeStream(4, 6)
-        self.assertEquals(s.length, 6)
-        self.assertEquals(bufstr(s.read()), self.text[4:10])
-        self.assertEquals(s.read(), None)
-        self.assertEquals(s.length, 0)
-
-class FileStreamTest(SimpleStreamTests, unittest.TestCase):
-    def makeStream(self, *args, **kw):
-        return stream.FileStream(self.f, *args, **kw)
-
-    def setUp(self):
-        """
-        Create a file containing C{self.text} to be streamed.
-        """
-        f = tempfile.TemporaryFile('w+')
-        f.write(self.text)
-        f.seek(0, 0)
-        self.f = f
-
-    def test_close(self):
-        s = self.makeStream()
-        s.close()
-
-        self.assertEquals(s.length, 0)
-        # Make sure close doesn't close file
-        # would raise exception if f is closed
-        self.f.seek(0, 0)
-
-    def test_read2(self):
-        s = self.makeStream(0)
-        s.CHUNK_SIZE = 6
-        self.assertEquals(s.length, 10)
-        self.assertEquals(bufstr(s.read()), self.text[0:6])
-        self.assertEquals(bufstr(s.read()), self.text[6:10])
-        self.assertEquals(s.read(), None)
-
-        s = self.makeStream(0)
-        s.CHUNK_SIZE = 5
-        self.assertEquals(s.length, 10)
-        self.assertEquals(bufstr(s.read()), self.text[0:5])
-        self.assertEquals(bufstr(s.read()), self.text[5:10])
-        self.assertEquals(s.read(), None)
-
-        s = self.makeStream(0, 20)
-        self.assertEquals(s.length, 20)
-        self.assertEquals(bufstr(s.read()), self.text)
-        self.assertRaises(RuntimeError, s.read) # ran out of data
-
-class MMapFileStreamTest(SimpleStreamTests, unittest.TestCase):
-    text = SimpleStreamTests.text
-    text = text * (stream.MMAP_THRESHOLD // len(text) + 1)
-
-    def makeStream(self, *args, **kw):
-        return stream.FileStream(self.f, *args, **kw)
-
-    def setUp(self):
-        """
-        Create a file containing C{self.text}, which should be long enough to
-        trigger the mmap-case in L{stream.FileStream}.
-        """
-        f = tempfile.TemporaryFile('w+')
-        f.write(self.text)
-        f.seek(0, 0)
-        self.f = f
-
-    def test_mmapwrapper(self):
-        self.assertRaises(TypeError, stream.mmapwrapper)
-        self.assertRaises(TypeError, stream.mmapwrapper, offset = 0)
-        self.assertRaises(TypeError, stream.mmapwrapper, offset = None)
-
-    if not stream.mmap:
-        test_mmapwrapper.skip = 'mmap not supported here'
-
-class MemoryStreamTest(SimpleStreamTests, unittest.TestCase):
-    def makeStream(self, *args, **kw):
-        return stream.MemoryStream(self.text, *args, **kw)
-
-    def test_close(self):
-        s = self.makeStream()
-        s.close()
-        self.assertEquals(s.length, 0)
-
-    def test_read2(self):
-        self.assertRaises(ValueError, self.makeStream, 0, 20)
-
-
-testdata = """I was angry with my friend:
-I told my wrath, my wrath did end.
-I was angry with my foe:
-I told it not, my wrath did grow.
-
-And I water'd it in fears,
-Night and morning with my tears;
-And I sunned it with smiles,
-And with soft deceitful wiles.
-
-And it grew both day and night,
-Till it bore an apple bright;
-And my foe beheld it shine,
-And he knew that is was mine,
-
-And into my garden stole
-When the night had veil'd the pole:
-In the morning glad I see
-My foe outstretch'd beneath the tree"""
-
-class TestSubstream(unittest.TestCase):
-
-    def setUp(self):
-        self.data = testdata
-        self.s = stream.MemoryStream(self.data)
-
-    def suckTheMarrow(self, s):
-        return ''.join(map(str, list(iter(s.read, None))))
-
-    def testStart(self):
-        s = stream.substream(self.s, 0, 11)
-        self.assertEquals('I was angry', self.suckTheMarrow(s))
-
-    def testNotStart(self):
-        s = stream.substream(self.s, 12, 26)
-        self.assertEquals('with my friend', self.suckTheMarrow(s))
-
-    def testReverseStartEnd(self):
-        self.assertRaises(ValueError, stream.substream, self.s, 26, 12)
-
-    def testEmptySubstream(self):
-        s = stream.substream(self.s, 11, 11)
-        self.assertEquals('', self.suckTheMarrow(s))
-
-    def testEnd(self):
-        size = len(self.data)
-        s = stream.substream(self.s, size-4, size)
-        self.assertEquals('tree', self.suckTheMarrow(s))
-
-    def testPastEnd(self):
-        size = len(self.data)
-        self.assertRaises(ValueError, stream.substream, self.s, size-4, size+8)
-
-
-class TestBufferedStream(unittest.TestCase):
-
-    def setUp(self):
-        self.data = testdata.replace('\n', '\r\n')
-        s = stream.MemoryStream(self.data)
-        self.s = stream.BufferedStream(s)
-
-    def _cbGotData(self, data, expected):
-        self.assertEqual(data, expected)
-
-    def test_readline(self):
-        """Test that readline reads a line."""
-        d = self.s.readline()
-        d.addCallback(self._cbGotData, 'I was angry with my friend:\r\n')
-        return d
-
-    def test_readlineWithSize(self):
-        """Test the size argument to readline"""
-        d = self.s.readline(size = 5)
-        d.addCallback(self._cbGotData, 'I was')
-        return d
-
-    def test_readlineWithBigSize(self):
-        """Test the size argument when it's bigger than the length of the line."""
-        d = self.s.readline(size = 40)
-        d.addCallback(self._cbGotData, 'I was angry with my friend:\r\n')
-        return d
-
-    def test_readlineWithZero(self):
-        """Test readline with size = 0."""
-        d = self.s.readline(size = 0)
-        d.addCallback(self._cbGotData, '')
-        return d
-
-    def test_readlineFinished(self):
-        """Test readline on a finished stream."""
-        nolines = len(self.data.split('\r\n'))
-        for i in range(nolines):
-            self.s.readline()
-        d = self.s.readline()
-        d.addCallback(self._cbGotData, '')
-        return d
-
-    def test_readlineNegSize(self):
-        """Ensure that readline with a negative size raises an exception."""
-        self.assertRaises(ValueError, self.s.readline, size = -1)
-
-    def test_readlineSizeInDelimiter(self):
-        """
-        Test behavior of readline when size falls inside the
-        delimiter.
-        """
-        d = self.s.readline(size=28)
-        d.addCallback(self._cbGotData, "I was angry with my friend:\r")
-        d.addCallback(lambda _: self.s.readline())
-        d.addCallback(self._cbGotData, "\nI told my wrath, my wrath did end.\r\n")
-
-    def test_readExactly(self):
-        """Make sure readExactly with no arg reads all the data."""
-        d = self.s.readExactly()
-        d.addCallback(self._cbGotData, self.data)
-        return d
-
-    def test_readExactlyLimited(self):
-        """
-        Test readExactly with a number.
-        """
-        d = self.s.readExactly(10)
-        d.addCallback(self._cbGotData, self.data[:10])
-        return d
-
-    def test_readExactlyBig(self):
-        """
-        Test readExactly with a number larger than the size of the
-        datastream.
-        """
-        d = self.s.readExactly(100000)
-        d.addCallback(self._cbGotData, self.data)
-        return d
-
-    def test_read(self):
-        """
-        Make sure read() also functions. (note that this test uses
-        an implementation detail of this particular stream. s.read()
-        isn't guaranteed to return self.data on all streams.)
-        """
-        self.assertEqual(str(self.s.read()), self.data)
-
-class TestStreamer:
-    implements(stream.IStream, stream.IByteStream)
-
-    length = None
-
-    readCalled=0
-    closeCalled=0
-
-    def __init__(self, list):
-        self.list = list
-
-    def read(self):
-        self.readCalled+=1
-        if self.list:
-            return self.list.pop(0)
-        return None
-
-    def close(self):
-        self.closeCalled+=1
-        self.list = []
-
-class FallbackSplitTest(unittest.TestCase):
-    def test_split(self):
-        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
-        left,right = stream.fallbackSplit(s, 5)
-        self.assertEquals(left.length, 5)
-        self.assertEquals(right.length, None)
-        self.assertEquals(bufstr(left.read()), 'abcd')
-        d = left.read()
-        d.addCallback(self._cbSplit, left, right)
-        return d
-
-    def _cbSplit(self, result, left, right):
-        self.assertEquals(bufstr(result), 'e')
-        self.assertEquals(left.read(), None)
-
-        self.assertEquals(bufstr(right.read().result), 'fgh')
-        self.assertEquals(bufstr(right.read()), 'ijkl')
-        self.assertEquals(right.read(), None)
-
-    def test_split2(self):
-        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
-        left,right = stream.fallbackSplit(s, 4)
-
-        self.assertEquals(left.length, 4)
-        self.assertEquals(right.length, None)
-
-        self.assertEquals(bufstr(left.read()), 'abcd')
-        self.assertEquals(left.read(), None)
-
-        self.assertEquals(bufstr(right.read().result), 'efgh')
-        self.assertEquals(bufstr(right.read()), 'ijkl')
-        self.assertEquals(right.read(), None)
-
-    def test_splitsplit(self):
-        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
-        left,right = stream.fallbackSplit(s, 5)
-        left,middle = left.split(3)
-
-        self.assertEquals(left.length, 3)
-        self.assertEquals(middle.length, 2)
-        self.assertEquals(right.length, None)
-
-        self.assertEquals(bufstr(left.read()), 'abc')
-        self.assertEquals(left.read(), None)
-
-        self.assertEquals(bufstr(middle.read().result), 'd')
-        self.assertEquals(bufstr(middle.read().result), 'e')
-        self.assertEquals(middle.read(), None)
-
-        self.assertEquals(bufstr(right.read().result), 'fgh')
-        self.assertEquals(bufstr(right.read()), 'ijkl')
-        self.assertEquals(right.read(), None)
-
-    def test_closeboth(self):
-        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
-        left,right = stream.fallbackSplit(s, 5)
-        left.close()
-        self.assertEquals(s.closeCalled, 0)
-        right.close()
-
-        # Make sure nothing got read
-        self.assertEquals(s.readCalled, 0)
-        self.assertEquals(s.closeCalled, 1)
-
-    def test_closeboth_rev(self):
-        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
-        left,right = stream.fallbackSplit(s, 5)
-        right.close()
-        self.assertEquals(s.closeCalled, 0)
-        left.close()
-
-        # Make sure nothing got read
-        self.assertEquals(s.readCalled, 0)
-        self.assertEquals(s.closeCalled, 1)
-
-    def test_closeleft(self):
-        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
-        left,right = stream.fallbackSplit(s, 5)
-        left.close()
-        d = right.read()
-        d.addCallback(self._cbCloseleft, right)
-        return d
-
-    def _cbCloseleft(self, result, right):
-        self.assertEquals(bufstr(result), 'fgh')
-        self.assertEquals(bufstr(right.read()), 'ijkl')
-        self.assertEquals(right.read(), None)
-
-    def test_closeright(self):
-        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
-        left,right = stream.fallbackSplit(s, 3)
-        right.close()
-
-        self.assertEquals(bufstr(left.read()), 'abc')
-        self.assertEquals(left.read(), None)
-
-        self.assertEquals(s.closeCalled, 1)
-
-
-class ProcessStreamerTest(unittest.TestCase):
-
-    if interfaces.IReactorProcess(reactor, None) is None:
-        skip = "Platform lacks spawnProcess support, can't test process streaming."
-
-    def runCode(self, code, inputStream=None):
-        if inputStream is None:
-            inputStream = stream.MemoryStream("")
-        return stream.ProcessStreamer(inputStream, sys.executable,
-                                      [sys.executable, "-u", "-c", code],
-                                      os.environ)
-
-    def test_output(self):
-        p = self.runCode("import sys\nfor i in range(100): sys.stdout.write('x' * 1000)")
-        l = []
-        d = stream.readStream(p.outStream, l.append)
-        def verify(_):
-            self.assertEquals("".join(l), ("x" * 1000) * 100)
-        d2 = p.run()
-        return d.addCallback(verify).addCallback(lambda _: d2)
-
-    def test_errouput(self):
-        p = self.runCode("import sys\nfor i in range(100): sys.stderr.write('x' * 1000)")
-        l = []
-        d = stream.readStream(p.errStream, l.append)
-        def verify(_):
-            self.assertEquals("".join(l), ("x" * 1000) * 100)
-        p.run()
-        return d.addCallback(verify)
-
-    def test_input(self):
-        p = self.runCode("import sys\nsys.stdout.write(sys.stdin.read())",
-                         "hello world")
-        l = []
-        d = stream.readStream(p.outStream, l.append)
-        d2 = p.run()
-        def verify(_):
-            self.assertEquals("".join(l), "hello world")
-            return d2
-        return d.addCallback(verify)
-
-    def test_badexit(self):
-        p = self.runCode("raise ValueError")
-        l = []
-        from twisted.internet.error import ProcessTerminated
-        def verify(_):
-            self.assertEquals(l, [1])
-            self.assert_(p.outStream.closed)
-            self.assert_(p.errStream.closed)
-        return p.run().addErrback(lambda _: _.trap(ProcessTerminated) and l.append(1)).addCallback(verify)
-
-    def test_inputerror(self):
-        p = self.runCode("import sys\nsys.stdout.write(sys.stdin.read())",
-                         TestStreamer(["hello", defer.fail(ZeroDivisionError())]))
-        l = []
-        d = stream.readStream(p.outStream, l.append)
-        d2 = p.run()
-        def verify(_):
-            self.assertEquals("".join(l), "hello")
-            return d2
-        def cbVerified(ignored):
-            excs = self.flushLoggedErrors(ZeroDivisionError)
-            self.assertEqual(len(excs), 1)
-        return d.addCallback(verify).addCallback(cbVerified)
-
-    def test_processclosedinput(self):
-        p = self.runCode("import sys; sys.stdout.write(sys.stdin.read(3));" +
-                         "sys.stdin.close(); sys.stdout.write('def')",
-                         "abc123")
-        l = []
-        d = stream.readStream(p.outStream, l.append)
-        def verify(_):
-            self.assertEquals("".join(l), "abcdef")
-        d2 = p.run()
-        return d.addCallback(verify).addCallback(lambda _: d2)
-
-
-class AdapterTestCase(unittest.TestCase):
-
-    def test_adapt(self):
-        fName = self.mktemp()
-        f = file(fName, "w")
-        f.write("test")
-        f.close()
-        for i in ("test", buffer("test"), file(fName)):
-            s = stream.IByteStream(i)
-            self.assertEquals(str(s.read()), "test")
-            self.assertEquals(s.read(), None)
-
-
-class ReadStreamTestCase(unittest.TestCase):
-
-    def test_pull(self):
-        l = []
-        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
-        return stream.readStream(s, l.append).addCallback(
-            lambda _: self.assertEquals(l, ["abcd", "efgh", "ijkl"]))
-
-    def test_pullFailure(self):
-        l = []
-        s = TestStreamer(['abcd', defer.fail(RuntimeError()), 'ijkl'])
-        def test(result):
-            result.trap(RuntimeError)
-            self.assertEquals(l, ["abcd"])
-        return stream.readStream(s, l.append).addErrback(test)
-
-    def test_pullException(self):
-        class Failer:
-            def read(self): raise RuntimeError
-        return stream.readStream(Failer(), lambda _: None).addErrback(
-            lambda _: _.trap(RuntimeError))
-
-    def test_processingException(self):
-        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
-        return stream.readStream(s, lambda x: 1/0).addErrback(
-            lambda _: _.trap(ZeroDivisionError))
-
-
-
-class ProducerStreamTestCase(unittest.TestCase):
-
-    def test_failfinish(self):
-        p = stream.ProducerStream()
-        p.write("hello")
-        p.finish(RuntimeError())
-        self.assertEquals(p.read(), "hello")
-        d = p.read()
-        l = []
-        d.addErrback(lambda _: (l.append(1), _.trap(RuntimeError))).addCallback(
-            lambda _: self.assertEquals(l, [1]))
-        return d
-
-
-class CompoundStreamTest:
-    """
-    CompoundStream lets you combine many streams into one continuous stream.
-    For example, let's make a stream:
-    >>> s = stream.CompoundStream()
-
-    Then, add a couple streams:
-    >>> s.addStream(stream.MemoryStream("Stream1"))
-    >>> s.addStream(stream.MemoryStream("Stream2"))
-
-    The length is the sum of all the streams:
-    >>> s.length
-    14
-
-    We can read data from the stream:
-    >>> str(s.read())
-    'Stream1'
-
-    After having read some data, length is now smaller, as you might expect:
-    >>> s.length
-    7
-
-    So, continue reading...
-    >>> str(s.read())
-    'Stream2'
-
-    Now that the stream is exhausted:
-    >>> s.read() is None
-    True
-    >>> s.length
-    0
-
-    We can also create CompoundStream more easily like so:
-    >>> s = stream.CompoundStream(['hello', stream.MemoryStream(' world')])
-    >>> str(s.read())
-    'hello'
-    >>> str(s.read())
-    ' world'
-
-    For a more complicated example, let's try reading from a file:
-    >>> s = stream.CompoundStream()
-    >>> s.addStream(stream.FileStream(open(sibpath(__file__, "stream_data.txt"))))
-    >>> s.addStream("================")
-    >>> s.addStream(stream.FileStream(open(sibpath(__file__, "stream_data.txt"))))
-
-    Again, the length is the sum:
-    >>> int(s.length)
-    58
-
-    >>> str(s.read())
-    "We've got some text!\\n"
-    >>> str(s.read())
-    '================'
-
-    What if you close the stream?
-    >>> s.close()
-    >>> s.read() is None
-    True
-    >>> s.length
-    0
-
-    Error handling works using Deferreds:
-    >>> m = stream.MemoryStream("after")
-    >>> s = stream.CompoundStream([TestStreamer([defer.fail(ZeroDivisionError())]), m]) # z<
-    >>> l = []; x = s.read().addErrback(lambda _: l.append(1))
-    >>> l
-    [1]
-    >>> s.length
-    0
-    >>> m.length # streams after the failed one got closed
-    0
-
-    """
-
-
-__doctests__ = ['twext.web2.test.test_stream', 'twext.web2.stream']
-# TODO:
-# CompoundStreamTest
-# more tests for ProducerStreamTest
-# StreamProducerTest

Copied: CalendarServer/trunk/twext/web2/test/test_stream.py (from rev 5149, CalendarServer/branches/users/glyph/use-system-twisted/twext/web2/test/test_stream.py)
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_stream.py	                        (rev 0)
+++ CalendarServer/trunk/twext/web2/test/test_stream.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -0,0 +1,622 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for the stream implementations in L{twext.web2}.
+"""
+
+import tempfile, sys, os
+
+from zope.interface import implements
+
+# sibpath is *not* unused - the doctests use it.
+from twisted.python.util import sibpath
+from twisted.internet import reactor, defer, interfaces
+from twisted.trial import unittest
+from twext.web2 import stream
+
+
+def bufstr(data):
+    try:
+        return str(buffer(data))
+    except TypeError:
+        raise TypeError("%s doesn't conform to the buffer interface" % (data,))
+
+
+class SimpleStreamTests:
+    text = '1234567890'
+    def test_split(self):
+        for point in range(10):
+            s = self.makeStream(0)
+            a,b = s.split(point)
+            if point > 0:
+                self.assertEquals(bufstr(a.read()), self.text[:point])
+            self.assertEquals(a.read(), None)
+            if point < len(self.text):
+                self.assertEquals(bufstr(b.read()), self.text[point:])
+            self.assertEquals(b.read(), None)
+
+        for point in range(7):
+            s = self.makeStream(2, 6)
+            self.assertEquals(s.length, 6)
+            a,b = s.split(point)
+            if point > 0:
+                self.assertEquals(bufstr(a.read()), self.text[2:point+2])
+            self.assertEquals(a.read(), None)
+            if point < 6:
+                self.assertEquals(bufstr(b.read()), self.text[point+2:8])
+            self.assertEquals(b.read(), None)
+
+    def test_read(self):
+        s = self.makeStream()
+        self.assertEquals(s.length, len(self.text))
+        self.assertEquals(bufstr(s.read()), self.text)
+        self.assertEquals(s.read(), None)
+
+        s = self.makeStream(0, 4)
+        self.assertEquals(s.length, 4)
+        self.assertEquals(bufstr(s.read()), self.text[0:4])
+        self.assertEquals(s.read(), None)
+        self.assertEquals(s.length, 0)
+
+        s = self.makeStream(4, 6)
+        self.assertEquals(s.length, 6)
+        self.assertEquals(bufstr(s.read()), self.text[4:10])
+        self.assertEquals(s.read(), None)
+        self.assertEquals(s.length, 0)
+
+class FileStreamTest(SimpleStreamTests, unittest.TestCase):
+    def makeStream(self, *args, **kw):
+        return stream.FileStream(self.f, *args, **kw)
+
+    def setUp(self):
+        """
+        Create a file containing C{self.text} to be streamed.
+        """
+        f = tempfile.TemporaryFile('w+')
+        f.write(self.text)
+        f.seek(0, 0)
+        self.f = f
+
+    def test_close(self):
+        s = self.makeStream()
+        s.close()
+
+        self.assertEquals(s.length, 0)
+        # Make sure close doesn't close file
+        # would raise exception if f is closed
+        self.f.seek(0, 0)
+
+    def test_read2(self):
+        s = self.makeStream(0)
+        s.CHUNK_SIZE = 6
+        self.assertEquals(s.length, 10)
+        self.assertEquals(bufstr(s.read()), self.text[0:6])
+        self.assertEquals(bufstr(s.read()), self.text[6:10])
+        self.assertEquals(s.read(), None)
+
+        s = self.makeStream(0)
+        s.CHUNK_SIZE = 5
+        self.assertEquals(s.length, 10)
+        self.assertEquals(bufstr(s.read()), self.text[0:5])
+        self.assertEquals(bufstr(s.read()), self.text[5:10])
+        self.assertEquals(s.read(), None)
+
+        s = self.makeStream(0, 20)
+        self.assertEquals(s.length, 20)
+        self.assertEquals(bufstr(s.read()), self.text)
+        self.assertRaises(RuntimeError, s.read) # ran out of data
+
+class MMapFileStreamTest(SimpleStreamTests, unittest.TestCase):
+    text = SimpleStreamTests.text
+    text = text * (stream.MMAP_THRESHOLD // len(text) + 1)
+
+    def makeStream(self, *args, **kw):
+        return stream.FileStream(self.f, *args, **kw)
+
+    def setUp(self):
+        """
+        Create a file containing C{self.text}, which should be long enough to
+        trigger the mmap-case in L{stream.FileStream}.
+        """
+        f = tempfile.TemporaryFile('w+')
+        f.write(self.text)
+        f.seek(0, 0)
+        self.f = f
+
+    def test_mmapwrapper(self):
+        self.assertRaises(TypeError, stream.mmapwrapper)
+        self.assertRaises(TypeError, stream.mmapwrapper, offset = 0)
+        self.assertRaises(TypeError, stream.mmapwrapper, offset = None)
+
+    if not stream.mmap:
+        test_mmapwrapper.skip = 'mmap not supported here'
+
+class MemoryStreamTest(SimpleStreamTests, unittest.TestCase):
+    def makeStream(self, *args, **kw):
+        return stream.MemoryStream(self.text, *args, **kw)
+
+    def test_close(self):
+        s = self.makeStream()
+        s.close()
+        self.assertEquals(s.length, 0)
+
+    def test_read2(self):
+        self.assertRaises(ValueError, self.makeStream, 0, 20)
+
+
+testdata = """I was angry with my friend:
+I told my wrath, my wrath did end.
+I was angry with my foe:
+I told it not, my wrath did grow.
+
+And I water'd it in fears,
+Night and morning with my tears;
+And I sunned it with smiles,
+And with soft deceitful wiles.
+
+And it grew both day and night,
+Till it bore an apple bright;
+And my foe beheld it shine,
+And he knew that is was mine,
+
+And into my garden stole
+When the night had veil'd the pole:
+In the morning glad I see
+My foe outstretch'd beneath the tree"""
+
+class TestSubstream(unittest.TestCase):
+
+    def setUp(self):
+        self.data = testdata
+        self.s = stream.MemoryStream(self.data)
+
+    def suckTheMarrow(self, s):
+        return ''.join(map(str, list(iter(s.read, None))))
+
+    def testStart(self):
+        s = stream.substream(self.s, 0, 11)
+        self.assertEquals('I was angry', self.suckTheMarrow(s))
+
+    def testNotStart(self):
+        s = stream.substream(self.s, 12, 26)
+        self.assertEquals('with my friend', self.suckTheMarrow(s))
+
+    def testReverseStartEnd(self):
+        self.assertRaises(ValueError, stream.substream, self.s, 26, 12)
+
+    def testEmptySubstream(self):
+        s = stream.substream(self.s, 11, 11)
+        self.assertEquals('', self.suckTheMarrow(s))
+
+    def testEnd(self):
+        size = len(self.data)
+        s = stream.substream(self.s, size-4, size)
+        self.assertEquals('tree', self.suckTheMarrow(s))
+
+    def testPastEnd(self):
+        size = len(self.data)
+        self.assertRaises(ValueError, stream.substream, self.s, size-4, size+8)
+
+
+class TestBufferedStream(unittest.TestCase):
+
+    def setUp(self):
+        self.data = testdata.replace('\n', '\r\n')
+        s = stream.MemoryStream(self.data)
+        self.s = stream.BufferedStream(s)
+
+    def _cbGotData(self, data, expected):
+        self.assertEqual(data, expected)
+
+    def test_readline(self):
+        """Test that readline reads a line."""
+        d = self.s.readline()
+        d.addCallback(self._cbGotData, 'I was angry with my friend:\r\n')
+        return d
+
+    def test_readlineWithSize(self):
+        """Test the size argument to readline"""
+        d = self.s.readline(size = 5)
+        d.addCallback(self._cbGotData, 'I was')
+        return d
+
+    def test_readlineWithBigSize(self):
+        """Test the size argument when it's bigger than the length of the line."""
+        d = self.s.readline(size = 40)
+        d.addCallback(self._cbGotData, 'I was angry with my friend:\r\n')
+        return d
+
+    def test_readlineWithZero(self):
+        """Test readline with size = 0."""
+        d = self.s.readline(size = 0)
+        d.addCallback(self._cbGotData, '')
+        return d
+
+    def test_readlineFinished(self):
+        """Test readline on a finished stream."""
+        nolines = len(self.data.split('\r\n'))
+        for i in range(nolines):
+            self.s.readline()
+        d = self.s.readline()
+        d.addCallback(self._cbGotData, '')
+        return d
+
+    def test_readlineNegSize(self):
+        """Ensure that readline with a negative size raises an exception."""
+        self.assertRaises(ValueError, self.s.readline, size = -1)
+
+    def test_readlineSizeInDelimiter(self):
+        """
+        Test behavior of readline when size falls inside the
+        delimiter.
+        """
+        d = self.s.readline(size=28)
+        d.addCallback(self._cbGotData, "I was angry with my friend:\r")
+        d.addCallback(lambda _: self.s.readline())
+        d.addCallback(self._cbGotData, "\nI told my wrath, my wrath did end.\r\n")
+
+    def test_readExactly(self):
+        """Make sure readExactly with no arg reads all the data."""
+        d = self.s.readExactly()
+        d.addCallback(self._cbGotData, self.data)
+        return d
+
+    def test_readExactlyLimited(self):
+        """
+        Test readExactly with a number.
+        """
+        d = self.s.readExactly(10)
+        d.addCallback(self._cbGotData, self.data[:10])
+        return d
+
+    def test_readExactlyBig(self):
+        """
+        Test readExactly with a number larger than the size of the
+        datastream.
+        """
+        d = self.s.readExactly(100000)
+        d.addCallback(self._cbGotData, self.data)
+        return d
+
+    def test_read(self):
+        """
+        Make sure read() also functions. (note that this test uses
+        an implementation detail of this particular stream. s.read()
+        isn't guaranteed to return self.data on all streams.)
+        """
+        self.assertEqual(str(self.s.read()), self.data)
+
+class TestStreamer:
+    implements(stream.IStream, stream.IByteStream)
+
+    length = None
+
+    readCalled=0
+    closeCalled=0
+
+    def __init__(self, list):
+        self.list = list
+
+    def read(self):
+        self.readCalled+=1
+        if self.list:
+            return self.list.pop(0)
+        return None
+
+    def close(self):
+        self.closeCalled+=1
+        self.list = []
+
+class FallbackSplitTest(unittest.TestCase):
+    def test_split(self):
+        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
+        left,right = stream.fallbackSplit(s, 5)
+        self.assertEquals(left.length, 5)
+        self.assertEquals(right.length, None)
+        self.assertEquals(bufstr(left.read()), 'abcd')
+        d = left.read()
+        d.addCallback(self._cbSplit, left, right)
+        return d
+
+    def _cbSplit(self, result, left, right):
+        self.assertEquals(bufstr(result), 'e')
+        self.assertEquals(left.read(), None)
+
+        self.assertEquals(bufstr(right.read().result), 'fgh')
+        self.assertEquals(bufstr(right.read()), 'ijkl')
+        self.assertEquals(right.read(), None)
+
+    def test_split2(self):
+        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
+        left,right = stream.fallbackSplit(s, 4)
+
+        self.assertEquals(left.length, 4)
+        self.assertEquals(right.length, None)
+
+        self.assertEquals(bufstr(left.read()), 'abcd')
+        self.assertEquals(left.read(), None)
+
+        self.assertEquals(bufstr(right.read().result), 'efgh')
+        self.assertEquals(bufstr(right.read()), 'ijkl')
+        self.assertEquals(right.read(), None)
+
+    def test_splitsplit(self):
+        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
+        left,right = stream.fallbackSplit(s, 5)
+        left,middle = left.split(3)
+
+        self.assertEquals(left.length, 3)
+        self.assertEquals(middle.length, 2)
+        self.assertEquals(right.length, None)
+
+        self.assertEquals(bufstr(left.read()), 'abc')
+        self.assertEquals(left.read(), None)
+
+        self.assertEquals(bufstr(middle.read().result), 'd')
+        self.assertEquals(bufstr(middle.read().result), 'e')
+        self.assertEquals(middle.read(), None)
+
+        self.assertEquals(bufstr(right.read().result), 'fgh')
+        self.assertEquals(bufstr(right.read()), 'ijkl')
+        self.assertEquals(right.read(), None)
+
+    def test_closeboth(self):
+        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
+        left,right = stream.fallbackSplit(s, 5)
+        left.close()
+        self.assertEquals(s.closeCalled, 0)
+        right.close()
+
+        # Make sure nothing got read
+        self.assertEquals(s.readCalled, 0)
+        self.assertEquals(s.closeCalled, 1)
+
+    def test_closeboth_rev(self):
+        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
+        left,right = stream.fallbackSplit(s, 5)
+        right.close()
+        self.assertEquals(s.closeCalled, 0)
+        left.close()
+
+        # Make sure nothing got read
+        self.assertEquals(s.readCalled, 0)
+        self.assertEquals(s.closeCalled, 1)
+
+    def test_closeleft(self):
+        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
+        left,right = stream.fallbackSplit(s, 5)
+        left.close()
+        d = right.read()
+        d.addCallback(self._cbCloseleft, right)
+        return d
+
+    def _cbCloseleft(self, result, right):
+        self.assertEquals(bufstr(result), 'fgh')
+        self.assertEquals(bufstr(right.read()), 'ijkl')
+        self.assertEquals(right.read(), None)
+
+    def test_closeright(self):
+        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
+        left,right = stream.fallbackSplit(s, 3)
+        right.close()
+
+        self.assertEquals(bufstr(left.read()), 'abc')
+        self.assertEquals(left.read(), None)
+
+        self.assertEquals(s.closeCalled, 1)
+
+
+class ProcessStreamerTest(unittest.TestCase):
+
+    if interfaces.IReactorProcess(reactor, None) is None:
+        skip = "Platform lacks spawnProcess support, can't test process streaming."
+
+    def runCode(self, code, inputStream=None):
+        if inputStream is None:
+            inputStream = stream.MemoryStream("")
+        return stream.ProcessStreamer(inputStream, sys.executable,
+                                      [sys.executable, "-u", "-c", code],
+                                      os.environ)
+
+    def test_output(self):
+        p = self.runCode("import sys\nfor i in range(100): sys.stdout.write('x' * 1000)")
+        l = []
+        d = stream.readStream(p.outStream, l.append)
+        def verify(_):
+            self.assertEquals("".join(l), ("x" * 1000) * 100)
+        d2 = p.run()
+        return d.addCallback(verify).addCallback(lambda _: d2)
+
+    def test_errouput(self):
+        p = self.runCode("import sys\nfor i in range(100): sys.stderr.write('x' * 1000)")
+        l = []
+        d = stream.readStream(p.errStream, l.append)
+        def verify(_):
+            self.assertEquals("".join(l), ("x" * 1000) * 100)
+        p.run()
+        return d.addCallback(verify)
+
+    def test_input(self):
+        p = self.runCode("import sys\nsys.stdout.write(sys.stdin.read())",
+                         "hello world")
+        l = []
+        d = stream.readStream(p.outStream, l.append)
+        d2 = p.run()
+        def verify(_):
+            self.assertEquals("".join(l), "hello world")
+            return d2
+        return d.addCallback(verify)
+
+    def test_badexit(self):
+        p = self.runCode("raise ValueError")
+        l = []
+        from twisted.internet.error import ProcessTerminated
+        def verify(_):
+            self.assertEquals(l, [1])
+            self.assert_(p.outStream.closed)
+            self.assert_(p.errStream.closed)
+        return p.run().addErrback(lambda _: _.trap(ProcessTerminated) and l.append(1)).addCallback(verify)
+
+    def test_inputerror(self):
+        p = self.runCode("import sys\nsys.stdout.write(sys.stdin.read())",
+                         TestStreamer(["hello", defer.fail(ZeroDivisionError())]))
+        l = []
+        d = stream.readStream(p.outStream, l.append)
+        d2 = p.run()
+        def verify(_):
+            self.assertEquals("".join(l), "hello")
+            return d2
+        def cbVerified(ignored):
+            excs = self.flushLoggedErrors(ZeroDivisionError)
+            self.assertEqual(len(excs), 1)
+        return d.addCallback(verify).addCallback(cbVerified)
+
+    def test_processclosedinput(self):
+        p = self.runCode("import sys; sys.stdout.write(sys.stdin.read(3));" +
+                         "sys.stdin.close(); sys.stdout.write('def')",
+                         "abc123")
+        l = []
+        d = stream.readStream(p.outStream, l.append)
+        def verify(_):
+            self.assertEquals("".join(l), "abcdef")
+        d2 = p.run()
+        return d.addCallback(verify).addCallback(lambda _: d2)
+
+
+class AdapterTestCase(unittest.TestCase):
+
+    def test_adapt(self):
+        fName = self.mktemp()
+        f = file(fName, "w")
+        f.write("test")
+        f.close()
+        for i in ("test", buffer("test"), file(fName)):
+            s = stream.IByteStream(i)
+            self.assertEquals(str(s.read()), "test")
+            self.assertEquals(s.read(), None)
+
+
+class ReadStreamTestCase(unittest.TestCase):
+
+    def test_pull(self):
+        l = []
+        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
+        return stream.readStream(s, l.append).addCallback(
+            lambda _: self.assertEquals(l, ["abcd", "efgh", "ijkl"]))
+
+    def test_pullFailure(self):
+        l = []
+        s = TestStreamer(['abcd', defer.fail(RuntimeError()), 'ijkl'])
+        def test(result):
+            result.trap(RuntimeError)
+            self.assertEquals(l, ["abcd"])
+        return stream.readStream(s, l.append).addErrback(test)
+
+    def test_pullException(self):
+        class Failer:
+            def read(self): raise RuntimeError
+        return stream.readStream(Failer(), lambda _: None).addErrback(
+            lambda _: _.trap(RuntimeError))
+
+    def test_processingException(self):
+        s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
+        return stream.readStream(s, lambda x: 1/0).addErrback(
+            lambda _: _.trap(ZeroDivisionError))
+
+
+
+class ProducerStreamTestCase(unittest.TestCase):
+
+    def test_failfinish(self):
+        p = stream.ProducerStream()
+        p.write("hello")
+        p.finish(RuntimeError())
+        self.assertEquals(p.read(), "hello")
+        d = p.read()
+        l = []
+        d.addErrback(lambda _: (l.append(1), _.trap(RuntimeError))).addCallback(
+            lambda _: self.assertEquals(l, [1]))
+        return d
+
+
+class CompoundStreamTest:
+    """
+    CompoundStream lets you combine many streams into one continuous stream.
+    For example, let's make a stream:
+    >>> s = stream.CompoundStream()
+
+    Then, add a couple streams:
+    >>> s.addStream(stream.MemoryStream("Stream1"))
+    >>> s.addStream(stream.MemoryStream("Stream2"))
+
+    The length is the sum of all the streams:
+    >>> s.length
+    14
+
+    We can read data from the stream:
+    >>> str(s.read())
+    'Stream1'
+
+    After having read some data, length is now smaller, as you might expect:
+    >>> s.length
+    7
+
+    So, continue reading...
+    >>> str(s.read())
+    'Stream2'
+
+    Now that the stream is exhausted:
+    >>> s.read() is None
+    True
+    >>> s.length
+    0
+
+    We can also create CompoundStream more easily like so:
+    >>> s = stream.CompoundStream(['hello', stream.MemoryStream(' world')])
+    >>> str(s.read())
+    'hello'
+    >>> str(s.read())
+    ' world'
+
+    For a more complicated example, let's try reading from a file:
+    >>> s = stream.CompoundStream()
+    >>> s.addStream(stream.FileStream(open(sibpath(__file__, "stream_data.txt"))))
+    >>> s.addStream("================")
+    >>> s.addStream(stream.FileStream(open(sibpath(__file__, "stream_data.txt"))))
+
+    Again, the length is the sum:
+    >>> int(s.length)
+    58
+
+    >>> str(s.read())
+    "We've got some text!\\n"
+    >>> str(s.read())
+    '================'
+
+    What if you close the stream?
+    >>> s.close()
+    >>> s.read() is None
+    True
+    >>> s.length
+    0
+
+    Error handling works using Deferreds:
+    >>> m = stream.MemoryStream("after")
+    >>> s = stream.CompoundStream([TestStreamer([defer.fail(ZeroDivisionError())]), m]) # z<
+    >>> l = []; x = s.read().addErrback(lambda _: l.append(1))
+    >>> l
+    [1]
+    >>> s.length
+    0
+    >>> m.length # streams after the failed one got closed
+    0
+
+    """
+
+
+__doctests__ = ['twext.web2.test.test_stream', 'twext.web2.stream']
+# TODO:
+# CompoundStreamTest
+# more tests for ProducerStreamTest
+# StreamProducerTest

Modified: CalendarServer/trunk/twistedcaldav/__init__.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/__init__.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/__init__.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -15,7 +15,7 @@
 ##
 
 """
-WebDAV support for Twisted Web2.
+WebDAV support for Twext.Web2.
 
 See draft spec: http://ietf.webdav.org/caldav/draft-dusseault-caldav.txt
 """
@@ -33,7 +33,7 @@
 # Load in suitable file extension/content-type map from OS X
 #
 
-from twisted.web2.static import File, loadMimeTypes
+from twext.web2.static import File, loadMimeTypes
 
 File.contentTypes = loadMimeTypes(("/etc/apache2/mime.types", "/etc/httpd/mime.types",))
 
@@ -41,22 +41,22 @@
 # Register additional WebDAV XML elements
 #
 
-import twisted.web2.dav.davxml
+import twext.web2.dav.davxml
 import twistedcaldav.caldavxml
 import twistedcaldav.carddavxml
 import twistedcaldav.mkcolxml
 import twistedcaldav.customxml
 
-twisted.web2.dav.davxml.registerElements(twistedcaldav.caldavxml)
-twisted.web2.dav.davxml.registerElements(twistedcaldav.customxml)
-twisted.web2.dav.davxml.registerElements(twistedcaldav.carddavxml)
-twisted.web2.dav.davxml.registerElements(twistedcaldav.mkcolxml)
+twext.web2.dav.davxml.registerElements(twistedcaldav.caldavxml)
+twext.web2.dav.davxml.registerElements(twistedcaldav.customxml)
+twext.web2.dav.davxml.registerElements(twistedcaldav.carddavxml)
+twext.web2.dav.davxml.registerElements(twistedcaldav.mkcolxml)
 
 #
 # DefaultHTTPHandler
 #
 
-from twisted.web2.http_headers import DefaultHTTPHandler, last, singleHeader
+from twext.web2.http_headers import DefaultHTTPHandler, last, singleHeader
 
 DefaultHTTPHandler.updateParsers({
     "If-Schedule-Tag-Match": (last, str),

Modified: CalendarServer/trunk/twistedcaldav/accesslog.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/accesslog.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/accesslog.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -31,10 +31,10 @@
 
 from twisted.internet import protocol
 from twisted.protocols import amp
-from twisted.web2 import iweb
-from twisted.web2.dav import davxml
-from twisted.web2.log import BaseCommonAccessLoggingObserver
-from twisted.web2.log import LogWrapperResource
+from twext.web2 import iweb
+from twext.web2.dav import davxml
+from twext.web2.log import BaseCommonAccessLoggingObserver
+from twext.web2.log import LogWrapperResource
 
 from twext.log import Logger
 
@@ -391,6 +391,8 @@
         if self.protocol is not None:
             # XXX: Yeah we're not waiting for anything to happen here.
             #      but we will log an error.
+            if isinstance(message, unicode):
+                message = message.encode("utf-8")
             d = self.protocol.callRemote(LogMessage, message=message)
             d.addErrback(log.err)
         else:

Modified: CalendarServer/trunk/twistedcaldav/accounting.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/accounting.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/accounting.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -46,7 +46,10 @@
     """
     Determine if accounting is enabled for the given category.
     """
-    return config.AccountingCategories.get(category, False)
+    AccountingCategories = getattr(config, "AccountingCategories", None)
+    if AccountingCategories is None:
+        return False
+    return AccountingCategories.get(category, False)
 
 def accountingEnabledForPrincipal(principal):
     """

Modified: CalendarServer/trunk/twistedcaldav/authkerb.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/authkerb.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/authkerb.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -42,9 +42,9 @@
 
 from twisted.cred import checkers, credentials, error
 from twisted.internet.defer import succeed
-from twisted.web2 import responsecode
-from twisted.web2.auth.interfaces import ICredentialFactory
-from twisted.web2.dav.auth import IPrincipalCredentials
+from twext.web2 import responsecode
+from twext.web2.auth.interfaces import ICredentialFactory
+from twext.web2.dav.auth import IPrincipalCredentials
 
 from twext.log import LoggingMixIn
 

Modified: CalendarServer/trunk/twistedcaldav/cache.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/cache.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/cache.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -21,10 +21,10 @@
 from zope.interface import implements
 
 from twisted.internet.defer import succeed, maybeDeferred
-from twisted.web2.dav.util import allDataFromStream
-from twisted.web2.http import Response
-from twisted.web2.iweb import IResource
-from twisted.web2.stream import MemoryStream
+from twext.web2.dav.util import allDataFromStream
+from twext.web2.http import Response
+from twext.web2.iweb import IResource
+from twext.web2.stream import MemoryStream
 
 from twext.log import LoggingMixIn
 

Modified: CalendarServer/trunk/twistedcaldav/caldavxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/caldavxml.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/caldavxml.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -29,7 +29,7 @@
 
 from vobject.icalendar import utc, TimezoneComponent
 
-from twisted.web2.dav import davxml
+from twext.web2.dav import davxml
 
 from twext.log import Logger
 

Modified: CalendarServer/trunk/twistedcaldav/carddavxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/carddavxml.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/carddavxml.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -28,7 +28,7 @@
 from twistedcaldav.vcard import Property as iProperty
 from twistedcaldav.vcard import Component
 
-from twisted.web2.dav import davxml
+from twext.web2.dav import davxml
 
 ##
 # CardDAV objects

Modified: CalendarServer/trunk/twistedcaldav/client/pool.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/client/pool.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/client/pool.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -30,11 +30,12 @@
 from twisted.internet.defer import Deferred, inlineCallbacks, returnValue
 from twisted.internet.error import ConnectionLost, ConnectionDone, ConnectError
 from twisted.internet.protocol import ClientFactory
-from twisted.web2 import responsecode
-from twisted.web2.client.http import HTTPClientProtocol
-from twisted.web2.dav.util import allDataFromStream
-from twisted.web2.http import StatusResponse, HTTPError
-from twisted.web2.stream import MemoryStream
+from twext.web2 import responsecode
+from twext.web2.client.http import HTTPClientProtocol
+from twext.web2.http import StatusResponse, HTTPError
+from twext.web2.dav.util import allDataFromStream
+from twext.web2.stream import MemoryStream
+
 from twistedcaldav.config import config
 
 class PooledHTTPClientFactory(ClientFactory, LoggingMixIn):

Modified: CalendarServer/trunk/twistedcaldav/client/reverseproxy.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/client/reverseproxy.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/client/reverseproxy.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -22,9 +22,9 @@
 
 from zope.interface.declarations import implements
 
-from twisted.web2 import iweb
-from twisted.web2.client.http import ClientRequest
-from twisted.web2.resource import LeafResource
+from twext.web2 import iweb
+from twext.web2.client.http import ClientRequest
+from twext.web2.resource import LeafResource
 
 from twext.log import LoggingMixIn
 

Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/customxml.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -23,10 +23,10 @@
 change.
 """
 
-from twisted.web2.dav.davxml import dav_namespace
-from twisted.web2.dav.davxml import twisted_dav_namespace
-from twisted.web2.dav.element.base import twisted_private_namespace
-from twisted.web2.dav import davxml
+from twext.web2.dav.davxml import dav_namespace
+from twext.web2.dav.davxml import twisted_dav_namespace
+from twext.web2.dav.element.base import twisted_private_namespace
+from twext.web2.dav import davxml
 
 from twistedcaldav.ical import Component as iComponent
 

Modified: CalendarServer/trunk/twistedcaldav/directory/addressbook.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/addressbook.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/addressbook.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -28,11 +28,11 @@
 ]
 
 from twisted.internet.defer import succeed
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.http import HTTPError
-from twisted.web2.dav.util import joinURL
-from twisted.web2.dav.resource import TwistedACLInheritable, TwistedQuotaRootProperty
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.http import HTTPError
+from twext.web2.dav.util import joinURL
+from twext.web2.dav.resource import TwistedACLInheritable, TwistedQuotaRootProperty
 
 from twistedcaldav.config import config
 from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource

Modified: CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -31,7 +31,7 @@
 
 from twisted.internet.threads import deferToThread
 from twisted.cred.credentials import UsernamePassword
-from twisted.web2.auth.digest import DigestedCredentials
+from twext.web2.auth.digest import DigestedCredentials
 
 from twistedcaldav.config import config
 from twistedcaldav.directory import augment

Modified: CalendarServer/trunk/twistedcaldav/directory/calendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendar.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/calendar.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -28,11 +28,11 @@
 ]
 
 from twisted.internet.defer import succeed
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.http import HTTPError
-from twisted.web2.dav.util import joinURL
-from twisted.web2.dav.resource import TwistedACLInheritable
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.http import HTTPError
+from twext.web2.dav.util import joinURL
+from twext.web2.dav.resource import TwistedACLInheritable
 
 from twext.log import Logger
 

Modified: CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -30,12 +30,12 @@
 import time
 
 from twisted.internet.defer import succeed, inlineCallbacks, returnValue
-from twisted.web2 import responsecode
-from twisted.web2.http import HTTPError, StatusResponse
-from twisted.web2.dav import davxml
-from twisted.web2.dav.element.base import dav_namespace
-from twisted.web2.dav.util import joinURL
-from twisted.web2.dav.noneprops import NonePropertyStore
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2.dav import davxml
+from twext.web2.dav.element.base import dav_namespace
+from twext.web2.dav.util import joinURL
+from twext.web2.dav.noneprops import NonePropertyStore
 
 from twext.log import LoggingMixIn
 

Modified: CalendarServer/trunk/twistedcaldav/directory/digest.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/digest.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/digest.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -16,12 +16,12 @@
 
 from twisted.cred import error
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2.auth.digest import DigestCredentialFactory
-from twisted.web2.auth.digest import DigestedCredentials
-from twisted.web2.http_headers import Token
-from twisted.web2.http_headers import parseKeyValue
-from twisted.web2.http_headers import split
-from twisted.web2.http_headers import tokenize
+from twext.web2.auth.digest import DigestCredentialFactory
+from twext.web2.auth.digest import DigestedCredentials
+from twext.web2.http_headers import Token
+from twext.web2.http_headers import parseKeyValue
+from twext.web2.http_headers import split
+from twext.web2.http_headers import tokenize
 
 from twext.log import Logger
 
@@ -34,7 +34,7 @@
 log = Logger()
 
 """
-Overrides twisted.web2.auth.digest to allow specifying a qop value as a configuration parameter.
+Overrides twext.web2.auth.digest to allow specifying a qop value as a configuration parameter.
 Also adds an sqlite-based credentials cache that is multi-process safe.
 
 """
@@ -120,7 +120,7 @@
 
 class QopDigestCredentialFactory(DigestCredentialFactory):
     """
-    See twisted.web2.auth.digest.DigestCredentialFactory
+    See twext.web2.auth.digest.DigestCredentialFactory
     """
 
     def __init__(self, algorithm, qop, realm, namespace="DIGESTCREDENTIALS"):
@@ -198,7 +198,7 @@
         @type response: C{str}
         @param response: A string of comma seperated key=value pairs
 
-        @type request: L{twisted.web2.server.Request}
+        @type request: L{twext.web2.server.Request}
         @param request: the request being processed
 
         @return: L{DigestedCredentials}
@@ -256,7 +256,7 @@
         @param auth:        the response parameters.
         @type auth:         C{dict}
         @param request:     the request being processed.
-        @type request:      L{twisted.web2.server.Request}
+        @type request:      L{twext.web2.server.Request}
         
         @return:            C{True} if validated.
         @raise LoginFailed: if validation fails.

Modified: CalendarServer/trunk/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/directory.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/directory.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -35,7 +35,7 @@
 
 from twisted.cred.error import UnauthorizedLogin
 from twisted.cred.checkers import ICredentialsChecker
-from twisted.web2.dav.auth import IPrincipalCredentials
+from twext.web2.dav.auth import IPrincipalCredentials
 from twisted.internet.defer import succeed
 
 from twext.log import LoggingMixIn

Modified: CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -45,12 +45,12 @@
 from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks, returnValue, deferredGenerator
 from twisted.internet.threads import deferToThread
-from twisted.python.filepath import FilePath
-from twisted.web2.dav import davxml
-from twisted.web2.dav.element.base import twisted_dav_namespace, dav_namespace, parse_date, twisted_private_namespace
-from twisted.web2.dav.resource import DAVPropertyMixIn
-from twisted.web2.dav.util import joinURL
-from twisted.web2.http_headers import MimeType, generateContentType
+from twext.python.filepath import CachingFilePath as FilePath
+from twext.web2.dav import davxml
+from twext.web2.dav.element.base import twisted_dav_namespace, dav_namespace, parse_date, twisted_private_namespace
+from twext.web2.dav.resource import DAVPropertyMixIn
+from twext.web2.dav.util import joinURL
+from twext.web2.http_headers import MimeType, generateContentType
 
 
 from twistedcaldav import customxml, carddavxml

Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -41,12 +41,12 @@
 from twisted.python.failure import Failure
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.internet.defer import succeed
-from twisted.web2.auth.digest import DigestedCredentials
-from twisted.web2 import responsecode
-from twisted.web2.http import HTTPError
-from twisted.web2.dav import davxml
-from twisted.web2.dav.util import joinURL
-from twisted.web2.dav.noneprops import NonePropertyStore
+from twext.web2.auth.digest import DigestedCredentials
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError
+from twext.web2.dav import davxml
+from twext.web2.dav.util import joinURL
+from twext.web2.dav.noneprops import NonePropertyStore
 
 from twistedcaldav import carddavxml
 

Modified: CalendarServer/trunk/twistedcaldav/directory/sudo.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/sudo.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/sudo.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -23,7 +23,7 @@
     "SudoDirectoryService",
 ]
 
-from twisted.python.filepath import FilePath
+from twext.python.filepath import CachingFilePath as FilePath
 from twisted.cred.credentials import IUsernamePassword, IUsernameHashedPassword
 from twisted.cred.error import UnauthorizedLogin
 

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_augment.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_augment.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_augment.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -21,7 +21,7 @@
 from twistedcaldav.directory.xmlaugmentsparser import XMLAugmentsParser
 import cStringIO
 import os
-from twisted.python.filepath import FilePath
+from twext.python.filepath import CachingFilePath as FilePath
 
 xmlFile = os.path.join(os.path.dirname(__file__), "augments-test.xml")
 xmlFileDefault = os.path.join(os.path.dirname(__file__), "augments-test-default.xml")

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -17,8 +17,8 @@
 import os
 
 from twisted.internet.defer import inlineCallbacks
-from twisted.web2.dav import davxml
-from twisted.web2.test.test_server import SimpleRequest
+from twext.web2.dav import davxml
+from twext.web2.test.test_server import SimpleRequest
 
 from twistedcaldav import caldavxml
 from twistedcaldav.directory import augment

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_digest.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_digest.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_digest.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -22,14 +22,14 @@
 from twisted.internet import address
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.python import failure
-from twisted.web2.auth import digest
-from twisted.web2.auth.wrapper import UnauthorizedResponse
-from twisted.web2.test.test_server import SimpleRequest
+from twext.web2.auth import digest
+from twext.web2.auth.wrapper import UnauthorizedResponse
+from twext.web2.test.test_server import SimpleRequest
 
 from twistedcaldav.directory.digest import QopDigestCredentialFactory
 from twistedcaldav.test.util import TestCase
 from twistedcaldav.config import config
-from twisted.web2.auth.digest import DigestCredentialFactory
+from twext.web2.auth.digest import DigestCredentialFactory
 
 class FakeDigestCredentialFactory(QopDigestCredentialFactory):
     """

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_guidchange.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_guidchange.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_guidchange.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -19,9 +19,9 @@
 
 import os
 
-from twisted.web2.dav import davxml
-from twisted.web2.dav.resource import AccessDeniedError
-from twisted.web2.test.test_server import SimpleRequest
+from twext.web2.dav import davxml
+from twext.web2.dav.resource import AccessDeniedError
+from twext.web2.test.test_server import SimpleRequest
 
 from twistedcaldav.directory.xmlfile import XMLDirectoryService
 from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_modify.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_modify.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_modify.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -18,7 +18,7 @@
 from twistedcaldav.config import config
 from twistedcaldav.test.util import TestCase
 from calendarserver.tools.util import getDirectory
-from twisted.python.filepath import FilePath
+from twext.python.filepath import CachingFilePath as FilePath
 from twistedcaldav.directory.directory import DirectoryError
 
 

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -19,7 +19,7 @@
 except ImportError:
     pass
 else:
-    import twisted.web2.auth.digest
+    import twext.web2.auth.digest
     import twistedcaldav.directory.test.util
     from twistedcaldav.directory import augment
     from twisted.internet.defer import inlineCallbacks
@@ -95,7 +95,7 @@
             )
 
             digestFields = {}
-            digested = twisted.web2.auth.digest.DigestedCredentials("user", "GET", "example.com", digestFields, None)
+            digested = twext.web2.auth.digest.DigestedCredentials("user", "GET", "example.com", digestFields, None)
 
             self.assertFalse(record.verifyCredentials(digested))
 
@@ -134,7 +134,7 @@
 
             record.digestcache = {}
             record.digestcache["/"] = response
-            digested = twisted.web2.auth.digest.DigestedCredentials("user", "GET", "example.com", digestFields, None)
+            digested = twext.web2.auth.digest.DigestedCredentials("user", "GET", "example.com", digestFields, None)
 
             self.assertTrue(record.verifyCredentials(digested))
 

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -18,10 +18,10 @@
 
 from twisted.cred.credentials import UsernamePassword
 from twisted.internet.defer import inlineCallbacks
-from twisted.web2.dav import davxml
-from twisted.web2.dav.fileop import rmdir
-from twisted.web2.dav.resource import AccessDeniedError
-from twisted.web2.test.test_server import SimpleRequest
+from twext.web2.dav import davxml
+from twext.web2.dav.fileop import rmdir
+from twext.web2.dav.resource import AccessDeniedError
+from twext.web2.test.test_server import SimpleRequest
 
 from twistedcaldav.static import CalendarHomeProvisioningFile
 from twistedcaldav.config import config

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -16,7 +16,7 @@
 
 from twisted.internet.defer import DeferredList, inlineCallbacks, returnValue,\
     succeed
-from twisted.web2.dav import davxml
+from twext.web2.dav import davxml
 
 from twistedcaldav.directory.directory import DirectoryService
 from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile,\

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_sudo.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_sudo.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_sudo.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -15,7 +15,7 @@
 ##
 import os
 
-from twisted.python.filepath import FilePath
+from twext.python.filepath import CachingFilePath as FilePath
 
 import twistedcaldav.directory.test.util
 from twistedcaldav.directory.sudo import SudoDirectoryService

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -16,7 +16,7 @@
 
 import os
 
-from twisted.python.filepath import FilePath
+from twext.python.filepath import CachingFilePath as FilePath
 
 from twistedcaldav.test.util import TestCase
 from twistedcaldav.directory import augment

Modified: CalendarServer/trunk/twistedcaldav/directory/test/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/util.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/test/util.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -16,7 +16,7 @@
 
 from twisted.trial.unittest import SkipTest
 from twisted.cred.credentials import UsernamePassword
-from twisted.web2.auth.digest import DigestedCredentials, calcResponse, calcHA1
+from twext.web2.auth.digest import DigestedCredentials, calcResponse, calcHA1
 
 from twistedcaldav.directory.directory import DirectoryService
 from twistedcaldav.directory.directory import UnknownRecordTypeError

Modified: CalendarServer/trunk/twistedcaldav/directory/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/util.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/util.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -40,7 +40,7 @@
 
 import errno
 import time
-from twisted.python.filepath import FilePath
+from twext.python.filepath import CachingFilePath as FilePath
 
 class NotFilePath(FilePath):
     """

Modified: CalendarServer/trunk/twistedcaldav/directory/wiki.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/wiki.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/wiki.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -24,14 +24,14 @@
 ]
 
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2.dav import davxml
+from twext.web2.dav import davxml
 from twisted.web.xmlrpc import Proxy, Fault
-from twisted.web2.http import HTTPError, StatusResponse
-from twisted.web2.auth.wrapper import UnauthorizedResponse
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2.auth.wrapper import UnauthorizedResponse
 
 from twext.log import Logger
 
-from twisted.web2.dav.resource import TwistedACLInheritable
+from twext.web2.dav.resource import TwistedACLInheritable
 from twistedcaldav.config import config
 from twistedcaldav.directory.directory import (DirectoryService,
                                                DirectoryRecord,

Modified: CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -25,7 +25,7 @@
 
 import xml.dom.minidom
 
-from twisted.python.filepath import FilePath
+from twext.python.filepath import CachingFilePath as FilePath
 
 from twext.log import Logger
 

Modified: CalendarServer/trunk/twistedcaldav/directory/xmlfile.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/xmlfile.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directory/xmlfile.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -27,8 +27,8 @@
 import os, pwd, grp
 
 from twisted.cred.credentials import UsernamePassword
-from twisted.web2.auth.digest import DigestedCredentials
-from twisted.python.filepath import FilePath
+from twext.web2.auth.digest import DigestedCredentials
+from twext.python.filepath import CachingFilePath as FilePath
 from twistedcaldav.config import config
 
 from twistedcaldav.config import fullServerPath

Modified: CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -27,10 +27,10 @@
 from twext.log import Logger
 from twisted.internet.defer import succeed, inlineCallbacks, maybeDeferred, returnValue
 from twisted.python.reflect import namedClass
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.resource import TwistedACLInheritable
-from twisted.web2.http import HTTPError, StatusResponse
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.resource import TwistedACLInheritable
+from twext.web2.http import HTTPError, StatusResponse
 
 from twistedcaldav.config import config
 from twistedcaldav.resource import CalDAVResource

Modified: CalendarServer/trunk/twistedcaldav/dropbox.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/dropbox.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/dropbox.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -24,9 +24,9 @@
 ]
 
 from twext.web2.dav.davxml import ErrorResponse
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.resource import DAVResource, TwistedACLInheritable
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.resource import DAVResource, TwistedACLInheritable
 
 from twext.log import Logger
 

Modified: CalendarServer/trunk/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/extensions.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/extensions.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -37,24 +37,24 @@
 from twisted.internet.defer import succeed, DeferredList, inlineCallbacks, returnValue
 from twisted.internet.defer import maybeDeferred
 from twisted.cred.error import LoginFailed, UnauthorizedLogin
-from twisted.web2 import responsecode
-from twisted.web2.auth.wrapper import UnauthorizedResponse
-from twisted.web2.http import HTTPError, Response, RedirectResponse
-from twisted.web2.http import StatusResponse
-from twisted.web2.http_headers import MimeType
-from twisted.web2.stream import FileStream
-from twisted.web2.static import MetaDataMixin
-from twisted.web2.dav import davxml
-from twisted.web2.dav.auth import PrincipalCredentials
-from twisted.web2.dav.davxml import dav_namespace
-from twisted.web2.dav.http import MultiStatusResponse
-from twisted.web2.dav.idav import IDAVPrincipalResource
-from twisted.web2.dav.static import DAVFile as SuperDAVFile
-from twisted.web2.dav.resource import DAVResource as SuperDAVResource
-from twisted.web2.dav.resource import DAVPrincipalResource as SuperDAVPrincipalResource
-from twisted.web2.dav.util import joinURL
-from twisted.web2.dav.method import prop_common
-from twisted.web2.dav.method.report import max_number_of_matches
+from twext.web2 import responsecode
+from twext.web2.auth.wrapper import UnauthorizedResponse
+from twext.web2.http import HTTPError, Response, RedirectResponse
+from twext.web2.http import StatusResponse
+from twext.web2.http_headers import MimeType
+from twext.web2.stream import FileStream
+from twext.web2.static import MetaDataMixin
+from twext.web2.dav import davxml
+from twext.web2.dav.auth import PrincipalCredentials
+from twext.web2.dav.davxml import dav_namespace
+from twext.web2.dav.http import MultiStatusResponse
+from twext.web2.dav.idav import IDAVPrincipalResource
+from twext.web2.dav.static import DAVFile as SuperDAVFile
+from twext.web2.dav.resource import DAVResource as SuperDAVResource
+from twext.web2.dav.resource import DAVPrincipalResource as SuperDAVPrincipalResource
+from twext.web2.dav.util import joinURL
+from twext.web2.dav.method import prop_common
+from twext.web2.dav.method.report import max_number_of_matches
 
 from twext.log import Logger, LoggingMixIn
 
@@ -70,7 +70,7 @@
 #
 # Alter logger for some twisted stuff
 #
-import twisted
+import twext
 for m in (
     "web2.dav.fileop",
     "web2.dav.element.base",
@@ -96,7 +96,7 @@
     "web2.dav.util",
     "web2.dav.xattrprops",
 ):
-    submodule(twisted, m).log = Logger("twisted." + m)
+    submodule(twext, m).log = Logger("twext." + m)
 del m
 
 class SudoSACLMixin (object):
@@ -482,7 +482,7 @@
 
 class DAVResource (DirectoryPrincipalPropertySearchMixIn, SudoSACLMixin, SuperDAVResource, LoggingMixIn):
     """
-    Extended L{twisted.web2.dav.resource.DAVResource} implementation.
+    Extended L{twext.web2.dav.resource.DAVResource} implementation.
     """
     def renderHTTP(self, request):
         log.info("%s %s %s" % (request.method, urllib.unquote(request.uri), "HTTP/%s.%s" % request.clientproto))
@@ -681,7 +681,7 @@
 
 class DAVPrincipalResource (DirectoryPrincipalPropertySearchMixIn, SuperDAVPrincipalResource, LoggingMixIn):
     """
-    Extended L{twisted.web2.dav.static.DAVFile} implementation.
+    Extended L{twext.web2.dav.static.DAVFile} implementation.
     """
 
     liveProperties = tuple(SuperDAVPrincipalResource.liveProperties) + (
@@ -760,7 +760,7 @@
 
 class DAVFile (SudoSACLMixin, SuperDAVFile, LoggingMixIn):
     """
-    Extended L{twisted.web2.dav.static.DAVFile} implementation.
+    Extended L{twext.web2.dav.static.DAVFile} implementation.
     """
     def readProperty(self, property, request):
         if type(property) is tuple:

Modified: CalendarServer/trunk/twistedcaldav/fileops.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/fileops.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/fileops.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -18,9 +18,9 @@
 Various file utilities.
 """
 
-from twisted.web2.dav.fileop import copy
-from twisted.web2.dav.fileop import put
-from twisted.web2.dav.xattrprops import xattrPropertyStore
+from twext.web2.dav.fileop import copy
+from twext.web2.dav.fileop import put
+from twext.web2.dav.xattrprops import xattrPropertyStore
 
 # This class simulates a DAVFile with enough information for use with xattrPropertyStore.
 class FakeXAttrResource(object):

Modified: CalendarServer/trunk/twistedcaldav/freebusyurl.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/freebusyurl.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/freebusyurl.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -24,14 +24,14 @@
 
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.python import log
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
 from twext.web2.dav.davxml import ErrorResponse
-from twisted.web2.http import HTTPError
-from twisted.web2.http import Response
-from twisted.web2.http import StatusResponse
-from twisted.web2.http_headers import MimeType
-from twisted.web2.stream import MemoryStream
+from twext.web2.http import HTTPError
+from twext.web2.http import Response
+from twext.web2.http import StatusResponse
+from twext.web2.http_headers import MimeType
+from twext.web2.stream import MemoryStream
 
 from twistedcaldav import caldavxml
 from twistedcaldav.caldavxml import TimeRange

Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/ical.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -42,8 +42,8 @@
 from vobject.base import Component as vComponent, ContentLine as vContentLine, ParseError as vParseError
 from vobject.icalendar import TimezoneComponent, dateTimeToString, deltaToOffset, getTransition, stringToDate, stringToDateTime, stringToDurations, utc
 
-from twisted.web2.dav.util import allDataFromStream
-from twisted.web2.stream import IStream
+from twext.web2.dav.util import allDataFromStream
+from twext.web2.stream import IStream
 
 from twext.log import Logger
 

Modified: CalendarServer/trunk/twistedcaldav/icaldav.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/icaldav.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/icaldav.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -23,7 +23,7 @@
     "ICalendarPrincipalResource",
 ]
 
-from twisted.web2.dav.idav import IDAVResource
+from twext.web2.dav.idav import IDAVResource
 
 class ICalDAVResource(IDAVResource):
     """

Modified: CalendarServer/trunk/twistedcaldav/mail.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/mail.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/mail.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -44,13 +44,15 @@
 from twisted.plugin import IPlugin
 from twisted.python.usage import Options, UsageError
 from twisted.web import client
-from twisted.web2 import server, responsecode
-from twisted.web2.channel.http import HTTPFactory
-from twisted.web2.dav import davxml
-from twisted.web2.dav.noneprops import NonePropertyStore
-from twisted.web2.http import Response, HTTPError
-from twisted.web2.http_headers import MimeType
 
+from twext.web2 import server, responsecode
+from twext.web2.channel.http import HTTPFactory
+from twext.web2.dav import davxml
+from twext.web2.dav.noneprops import NonePropertyStore
+from twext.web2.http import Response, HTTPError
+from twext.web2.http_headers import MimeType
+
+
 from twext.log import Logger, LoggingMixIn
 
 from twistedcaldav import ical, caldavxml
@@ -65,8 +67,6 @@
 from twistedcaldav.static import CalDAVFile, deliverSchedulePrivilegeSet
 from twistedcaldav.util import AuthorizedHTTPGetter
 from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
-from twistedcaldav.sql import AbstractSQLDatabase
-from twistedcaldav.localization import translationTo
 
 from calendarserver.util import getRootResource
 

Modified: CalendarServer/trunk/twistedcaldav/memcacheprops.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/memcacheprops.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/memcacheprops.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -36,9 +36,9 @@
 
 from twext.log import LoggingMixIn, Logger
 
-from twisted.python.filepath import FilePath
-from twisted.web2 import responsecode
-from twisted.web2.http import HTTPError, StatusResponse
+from twext.python.filepath import CachingFilePath as FilePath
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError, StatusResponse
 
 from twistedcaldav.config import config
 

Modified: CalendarServer/trunk/twistedcaldav/method/copymove.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/copymove.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/copymove.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -23,12 +23,12 @@
 from urlparse import urlsplit
 
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2 import responsecode
-from twisted.web2.filter.location import addLocation
-from twisted.web2.dav import davxml
+from twext.web2 import responsecode
+from twext.web2.filter.location import addLocation
+from twext.web2.dav import davxml
 from twext.web2.dav.davxml import ErrorResponse
-from twisted.web2.dav.util import parentForURL
-from twisted.web2.http import StatusResponse, HTTPError
+from twext.web2.dav.util import parentForURL
+from twext.web2.http import StatusResponse, HTTPError
 
 from twext.log import Logger
 

Modified: CalendarServer/trunk/twistedcaldav/method/copymove_contact.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/copymove_contact.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/copymove_contact.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -23,12 +23,12 @@
 from urlparse import urlsplit
 
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2 import responsecode
-from twisted.web2.filter.location import addLocation
-from twisted.web2.dav import davxml
-from twisted.web2.dav.http import ErrorResponse
-from twisted.web2.dav.util import parentForURL
-from twisted.web2.http import StatusResponse, HTTPError
+from twext.web2 import responsecode
+from twext.web2.filter.location import addLocation
+from twext.web2.dav import davxml
+from twext.web2.dav.http import ErrorResponse
+from twext.web2.dav.util import parentForURL
+from twext.web2.http import StatusResponse, HTTPError
 
 from twistedcaldav.carddavxml import carddav_namespace
 from twistedcaldav.method.put_addressbook_common import StoreAddressObjectResource

Modified: CalendarServer/trunk/twistedcaldav/method/delete.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/delete.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/delete.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -23,10 +23,10 @@
 from twext.log import Logger
 
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.util import parentForURL
-from twisted.web2.http import HTTPError
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.util import parentForURL
+from twext.web2.http import HTTPError
 
 from twistedcaldav.method.delete_common import DeleteResource
 

Modified: CalendarServer/trunk/twistedcaldav/method/delete_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/delete_common.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/delete_common.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -22,11 +22,11 @@
 __all__ = ["DeleteResource"]
 
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2 import responsecode
-from twisted.web2.dav.fileop import delete
-from twisted.web2.dav.http import ResponseQueue, MultiStatusResponse
-from twisted.web2.dav.util import joinURL
-from twisted.web2.http import HTTPError, StatusResponse
+from twext.web2 import responsecode
+from twext.web2.dav.fileop import delete
+from twext.web2.dav.http import ResponseQueue, MultiStatusResponse
+from twext.web2.dav.util import joinURL
+from twext.web2.http import HTTPError, StatusResponse
 
 from twext.log import Logger
 from twext.web2.dav.davxml import ErrorResponse

Modified: CalendarServer/trunk/twistedcaldav/method/get.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/get.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/get.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -21,11 +21,11 @@
 __all__ = ["http_GET"]
 
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2.dav import davxml
-from twisted.web2.http import HTTPError
-from twisted.web2.http import Response
-from twisted.web2.http_headers import MimeType
-from twisted.web2.stream import MemoryStream
+from twext.web2.dav import davxml
+from twext.web2.http import HTTPError
+from twext.web2.http import Response
+from twext.web2.http_headers import MimeType
+from twext.web2.stream import MemoryStream
 
 from twistedcaldav import caldavxml
 from twistedcaldav.caldavxml import ScheduleTag

Modified: CalendarServer/trunk/twistedcaldav/method/mkcalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/mkcalendar.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/mkcalendar.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -22,12 +22,12 @@
 
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.python.failure import Failure
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.http import MultiStatusResponse, PropertyStatusResponseQueue
-from twisted.web2.dav.util import davXMLFromStream
-from twisted.web2.dav.util import parentForURL
-from twisted.web2.http import HTTPError, StatusResponse
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.http import MultiStatusResponse, PropertyStatusResponseQueue
+from twext.web2.dav.util import davXMLFromStream
+from twext.web2.dav.util import parentForURL
+from twext.web2.http import HTTPError, StatusResponse
 
 from twext.log import Logger
 from twext.web2.dav.davxml import ErrorResponse

Modified: CalendarServer/trunk/twistedcaldav/method/mkcol.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/mkcol.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/mkcol.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -26,13 +26,13 @@
 from twisted.python import log
 from twisted.python.failure import Failure
 
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.http import ErrorResponse, MultiStatusResponse, PropertyStatusResponseQueue
-from twisted.web2.dav.util import davXMLFromStream
-from twisted.web2.dav.util import parentForURL
-from twisted.web2.http import HTTPError
-from twisted.web2.http import StatusResponse
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.http import ErrorResponse, MultiStatusResponse, PropertyStatusResponseQueue
+from twext.web2.dav.util import davXMLFromStream
+from twext.web2.dav.util import parentForURL
+from twext.web2.http import HTTPError
+from twext.web2.http import StatusResponse
 
 from twistedcaldav import caldavxml, carddavxml, mkcolxml
 from twistedcaldav.config import config
@@ -95,7 +95,7 @@
         doc = (yield davXMLFromStream(request.stream))
     except ValueError, e:
         log.err("Error while handling MKCOL: %s" % (e,))
-        # TODO: twisted.web2.dav 'MKCOL' tests demand this particular response
+        # TODO: twext.web2.dav 'MKCOL' tests demand this particular response
         # code, but should we really be looking at the XML content or the
         # content-type header?  It seems to me like this ought to be considered
         # a BAD_REQUEST if it claims to be XML but isn't, but an

Modified: CalendarServer/trunk/twistedcaldav/method/propfind.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/propfind.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/propfind.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -1,4 +1,4 @@
-# -*- test-case-name: twisted.web2.dav.test.test_prop.PROP.test_PROPFIND -*-
+# -*- test-case-name: twext.web2.dav.test.test_prop.PROP.test_PROPFIND -*-
 ##
 # Copyright (c) 2005-2008 Apple Computer, Inc. All rights reserved.
 #
@@ -29,12 +29,12 @@
 
 from twisted.python.failure import Failure
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2.http import HTTPError
-from twisted.web2 import responsecode
-from twisted.web2.http import StatusResponse
-from twisted.web2.dav import davxml
-from twisted.web2.dav.http import MultiStatusResponse, statusForFailure
-from twisted.web2.dav.util import normalizeURL, davXMLFromStream
+from twext.web2.http import HTTPError
+from twext.web2 import responsecode
+from twext.web2.http import StatusResponse
+from twext.web2.dav import davxml
+from twext.web2.dav.http import MultiStatusResponse, statusForFailure
+from twext.web2.dav.util import normalizeURL, davXMLFromStream
 
 from twext.log import Logger
 

Modified: CalendarServer/trunk/twistedcaldav/method/put.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/put.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -21,9 +21,9 @@
 __all__ = ["http_PUT"]
 
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2 import responsecode
-from twisted.web2.dav.util import allDataFromStream, parentForURL
-from twisted.web2.http import HTTPError, StatusResponse
+from twext.web2 import responsecode
+from twext.web2.dav.util import allDataFromStream, parentForURL
+from twext.web2.http import HTTPError, StatusResponse
 
 from twext.log import Logger
 from twext.web2.dav.davxml import ErrorResponse

Modified: CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -26,20 +26,20 @@
 from twisted.internet.defer import Deferred, inlineCallbacks, succeed
 from twisted.internet.defer import maybeDeferred, returnValue
 from twisted.python import failure
-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.element.base import PCDATAElement
-from twisted.web2.dav.fileop import delete
-from twisted.web2.dav.http import ErrorResponse
-from twisted.web2.dav.resource import TwistedGETContentMD5
-from twisted.web2.dav.stream import MD5StreamWrapper
-from twisted.web2.dav.util import joinURL, parentForURL
-from twisted.web2.http import HTTPError
-from twisted.web2.http import StatusResponse
-from twisted.web2.iweb import IResponse
-from twisted.web2.stream import MemoryStream
+from twext.python.filepath import CachingFilePath as FilePath
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.element.base import dav_namespace
+from twext.web2.dav.element.base import PCDATAElement
+from twext.web2.dav.fileop import delete
+from twext.web2.dav.http import ErrorResponse
+from twext.web2.dav.resource import TwistedGETContentMD5
+from twext.web2.dav.stream import MD5StreamWrapper
+from twext.web2.dav.util import joinURL, parentForURL
+from twext.web2.http import HTTPError
+from twext.web2.http import StatusResponse
+from twext.web2.iweb import IResponse
+from twext.web2.stream import MemoryStream
 
 from twistedcaldav.config import config
 from twistedcaldav.caldavxml import NumberOfRecurrencesWithinLimits
@@ -184,7 +184,7 @@
         """
         Function that does common PUT/COPY/MOVE behavior.
         
-        @param request:           the L{twisted.web2.server.Request} for the current HTTP request.
+        @param request:           the L{twext.web2.server.Request} for the current HTTP request.
         @param source:            the L{CalDAVFile} 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.

Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -29,20 +29,20 @@
 from twisted.internet.defer import Deferred, inlineCallbacks, succeed
 from twisted.internet.defer import returnValue
 from twisted.python import failure
-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.element.base import PCDATAElement
-from twisted.web2.dav.fileop import delete
-from twisted.web2.dav.resource import TwistedGETContentMD5
-from twisted.web2.dav.stream import MD5StreamWrapper
-from twisted.web2.dav.util import joinURL, parentForURL
-from twisted.web2.http import HTTPError
-from twisted.web2.http import StatusResponse
-from twisted.web2.http_headers import generateContentType, MimeType
-from twisted.web2.iweb import IResponse
-from twisted.web2.stream import MemoryStream
+from twext.python.filepath import CachingFilePath as FilePath
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.element.base import dav_namespace
+from twext.web2.dav.element.base import PCDATAElement
+from twext.web2.dav.fileop import delete
+from twext.web2.dav.resource import TwistedGETContentMD5
+from twext.web2.dav.stream import MD5StreamWrapper
+from twext.web2.dav.util import joinURL, parentForURL
+from twext.web2.http import HTTPError
+from twext.web2.http import StatusResponse
+from twext.web2.http_headers import generateContentType, MimeType
+from twext.web2.iweb import IResponse
+from twext.web2.stream import MemoryStream
 
 from twext.log import Logger
 from twext.web2.dav.davxml import ErrorResponse
@@ -214,7 +214,7 @@
         """
         Function that does common PUT/COPY/MOVE behavior.
         
-        @param request:           the L{twisted.web2.server.Request} for the current HTTP request.
+        @param request:           the L{twext.web2.server.Request} for the current HTTP request.
         @param source:            the L{CalDAVFile} 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.

Modified: CalendarServer/trunk/twistedcaldav/method/report.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/report.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -29,11 +29,11 @@
 import string
 
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2 import responsecode
-from twisted.web2.http import HTTPError, StatusResponse
-from twisted.web2.dav import davxml
-from twisted.web2.dav.element.parser import lookupElement
-from twisted.web2.dav.util import davXMLFromStream
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2.dav import davxml
+from twext.web2.dav.element.parser import lookupElement
+from twext.web2.dav.util import davXMLFromStream
 
 from twext.log import Logger
 from twext.web2.dav.davxml import ErrorResponse

Modified: CalendarServer/trunk/twistedcaldav/method/report_addressbook_multiget.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_addressbook_multiget.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/report_addressbook_multiget.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -24,13 +24,13 @@
 
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.python import log
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.element.base import dav_namespace
-from twisted.web2.dav.http import ErrorResponse, MultiStatusResponse
-from twisted.web2.dav.resource import AccessDeniedError
-from twisted.web2.dav.util import joinURL
-from twisted.web2.http import HTTPError, StatusResponse
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.element.base import dav_namespace
+from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
+from twext.web2.dav.resource import AccessDeniedError
+from twext.web2.dav.util import joinURL
+from twext.web2.http import HTTPError, StatusResponse
 
 from twistedcaldav import carddavxml
 from twistedcaldav.config import config

Modified: CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -22,12 +22,12 @@
 
 from twisted.internet.defer import succeed, inlineCallbacks, returnValue, maybeDeferred
 from twisted.python import log
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.http import ErrorResponse, MultiStatusResponse
-from twisted.web2.dav.method.report import NumberOfMatchesWithinLimits
-from twisted.web2.dav.util import joinURL
-from twisted.web2.http import HTTPError, StatusResponse
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
+from twext.web2.dav.method.report import NumberOfMatchesWithinLimits
+from twext.web2.dav.util import joinURL
+from twext.web2.http import HTTPError, StatusResponse
 
 from twistedcaldav import carddavxml
 from twistedcaldav.config import config

Modified: CalendarServer/trunk/twistedcaldav/method/report_calquery.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_calquery.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/report_calquery.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -26,13 +26,13 @@
 from twext.web2.dav.davxml import ErrorResponse
 
 from twisted.internet.defer import succeed, inlineCallbacks, returnValue
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.element.base import PCDATAElement
-from twisted.web2.dav.http import MultiStatusResponse
-from twisted.web2.dav.method.report import NumberOfMatchesWithinLimits
-from twisted.web2.dav.util import joinURL
-from twisted.web2.http import HTTPError, StatusResponse
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.element.base import PCDATAElement
+from twext.web2.dav.http import MultiStatusResponse
+from twext.web2.dav.method.report import NumberOfMatchesWithinLimits
+from twext.web2.dav.util import joinURL
+from twext.web2.http import HTTPError, StatusResponse
 
 from twistedcaldav.caldavxml import caldav_namespace,\
     NumberOfRecurrencesWithinLimits

Modified: CalendarServer/trunk/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_common.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/report_common.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -41,15 +41,15 @@
 
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.python.failure import Failure
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.element.base import WebDAVElement
-from twisted.web2.dav.http import statusForFailure
-from twisted.web2.dav.method.propfind import propertyName
-from twisted.web2.dav.method.report import NumberOfMatchesWithinLimits
-from twisted.web2.dav.method.report import max_number_of_matches
-from twisted.web2.dav.resource import AccessDeniedError
-from twisted.web2.http import HTTPError
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.element.base import WebDAVElement
+from twext.web2.dav.http import statusForFailure
+from twext.web2.dav.method.propfind import propertyName
+from twext.web2.dav.method.report import NumberOfMatchesWithinLimits
+from twext.web2.dav.method.report import max_number_of_matches
+from twext.web2.dav.resource import AccessDeniedError
+from twext.web2.http import HTTPError
 
 from twext.log import Logger
 

Modified: CalendarServer/trunk/twistedcaldav/method/report_freebusy.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_freebusy.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/report_freebusy.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -24,12 +24,12 @@
 from twext.web2.dav.davxml import ErrorResponse
 
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.method.report import NumberOfMatchesWithinLimits
-from twisted.web2.http import HTTPError, Response, StatusResponse
-from twisted.web2.http_headers import MimeType
-from twisted.web2.stream import MemoryStream
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.method.report import NumberOfMatchesWithinLimits
+from twext.web2.http import HTTPError, Response, StatusResponse
+from twext.web2.http_headers import MimeType
+from twext.web2.stream import MemoryStream
 
 from twistedcaldav import caldavxml
 from twistedcaldav.method import report_common

Modified: CalendarServer/trunk/twistedcaldav/method/report_multiget.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_multiget.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/report_multiget.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -26,12 +26,12 @@
 from twext.web2.dav.davxml import ErrorResponse
 
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.http import MultiStatusResponse
-from twisted.web2.dav.resource import AccessDeniedError
-from twisted.web2.dav.util import joinURL
-from twisted.web2.http import HTTPError, StatusResponse
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.http import MultiStatusResponse
+from twext.web2.dav.resource import AccessDeniedError
+from twext.web2.dav.util import joinURL
+from twext.web2.http import HTTPError, StatusResponse
 
 from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.method import report_common

Modified: CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -25,14 +25,14 @@
 
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.python.failure import Failure
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.element.base import WebDAVElement
-from twisted.web2.dav.http import MultiStatusResponse, statusForFailure
-from twisted.web2.dav.method.prop_common import responseForHref
-from twisted.web2.dav.method.propfind import propertyName
-from twisted.web2.dav.util import joinURL
-from twisted.web2.http import HTTPError
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.element.base import WebDAVElement
+from twext.web2.dav.http import MultiStatusResponse, statusForFailure
+from twext.web2.dav.method.prop_common import responseForHref
+from twext.web2.dav.method.propfind import propertyName
+from twext.web2.dav.util import joinURL
+from twext.web2.http import HTTPError
 
 from twistedcaldav.config import config
 

Modified: CalendarServer/trunk/twistedcaldav/mkcolxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/mkcolxml.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/mkcolxml.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -25,7 +25,7 @@
 See draft spec: http://ietf.webdav.org/caldav/draft-dusseault-caldav.txt
 """
 
-from twisted.web2.dav import davxml
+from twext.web2.dav import davxml
 
 ##
 # Extended MKCOL objects

Modified: CalendarServer/trunk/twistedcaldav/pdmonster.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/pdmonster.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/pdmonster.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -2,7 +2,7 @@
 from twisted.internet import address
 from twisted.protocols import amp
 
-from twisted.web2.resource import WrapperResource
+from twext.web2.resource import WrapperResource
 
 from twext.log import LoggingMixIn
 

Modified: CalendarServer/trunk/twistedcaldav/report_addressbook_findshared.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/report_addressbook_findshared.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/report_addressbook_findshared.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -33,12 +33,12 @@
 from plistlib import readPlist
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.python import log
-from twisted.python.filepath import FilePath
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.http import MultiStatusResponse
-from twisted.web2.dav.util import joinURL
-from twisted.web2.http import HTTPError, StatusResponse
+from twext.python.filepath import CachingFilePath as FilePath
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.http import MultiStatusResponse
+from twext.web2.dav.util import joinURL
+from twext.web2.http import HTTPError, StatusResponse
 
 from twistedcaldav import customxml
 from twistedcaldav.carddavxml import addressbookserver_namespace, carddav_namespace

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -41,19 +41,19 @@
 from twisted.internet import reactor
 from twisted.internet.defer import Deferred, maybeDeferred, succeed
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.auth import AuthenticationWrapper as SuperAuthenticationWrapper
-from twisted.web2.dav.davxml import dav_namespace
-from twisted.web2.dav.idav import IDAVPrincipalCollectionResource
-from twisted.web2.dav.resource import AccessDeniedError, DAVPrincipalCollectionResource
-from twisted.web2.dav.resource import TwistedACLInheritable
-from twisted.web2.dav.util import joinURL, parentForURL, unimplemented, normalizeURL
-from twisted.web2.http import HTTPError, RedirectResponse, StatusResponse, Response
-from twisted.web2.http_headers import MimeType
-from twisted.web2.iweb import IResponse
-from twisted.web2.stream import MemoryStream
-import twisted.web2.server
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.auth import AuthenticationWrapper as SuperAuthenticationWrapper
+from twext.web2.dav.davxml import dav_namespace
+from twext.web2.dav.idav import IDAVPrincipalCollectionResource
+from twext.web2.dav.resource import AccessDeniedError, DAVPrincipalCollectionResource
+from twext.web2.dav.resource import TwistedACLInheritable
+from twext.web2.dav.util import joinURL, parentForURL, unimplemented, normalizeURL
+from twext.web2.http import HTTPError, RedirectResponse, StatusResponse, Response
+from twext.web2.http_headers import MimeType
+from twext.web2.iweb import IResponse
+from twext.web2.stream import MemoryStream
+import twext.web2.server
 
 import twistedcaldav
 from twistedcaldav import caldavxml, customxml
@@ -72,9 +72,9 @@
 
 
 if twistedcaldav.__version__:
-    serverVersion = twisted.web2.server.VERSION + " TwistedCardDAV/" + twistedcaldav.__version__
+    serverVersion = twext.web2.server.VERSION + " TwistedCardDAV/" + twistedcaldav.__version__
 else:
-    serverVersion = twisted.web2.server.VERSION + " TwistedCardDAV/?"
+    serverVersion = twext.web2.server.VERSION + " TwistedCardDAV/?"
 
 class CalDAVComplianceMixIn(object):
 

Modified: CalendarServer/trunk/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/schedule.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/schedule.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -27,12 +27,12 @@
 from twext.web2.dav.davxml import ErrorResponse
 
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.util import joinURL, normalizeURL
-from twisted.web2.http import HTTPError
-from twisted.web2.http import Response
-from twisted.web2.http_headers import MimeType
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.util import joinURL, normalizeURL
+from twext.web2.http import HTTPError
+from twext.web2.http import Response
+from twext.web2.http_headers import MimeType
 
 from twistedcaldav import caldavxml
 from twistedcaldav.caldavxml import caldav_namespace

Modified: CalendarServer/trunk/twistedcaldav/scheduling/caldav.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/caldav.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/scheduling/caldav.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -26,11 +26,11 @@
 
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.python.failure import Failure
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.resource import AccessDeniedError
-from twisted.web2.dav.util import joinURL
-from twisted.web2.http import HTTPError
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.resource import AccessDeniedError
+from twext.web2.dav.util import joinURL
+from twext.web2.http import HTTPError
 
 from twistedcaldav import caldavxml
 from twistedcaldav.caldavxml import caldav_namespace

Modified: CalendarServer/trunk/twistedcaldav/scheduling/imip.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/imip.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/scheduling/imip.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -20,8 +20,8 @@
 from twext.log import Logger
 from twext.web2.dav.davxml import ErrorResponse
 
-from twisted.web2 import responsecode
-from twisted.web2.http import HTTPError
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError
 from twisted.web import client
 
 from twistedcaldav.caldavxml import caldav_namespace

Modified: CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -18,11 +18,11 @@
 from twext.web2.dav.davxml import ErrorResponse
 
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.util import joinURL
-from twisted.web2.dav.util import parentForURL
-from twisted.web2.http import HTTPError
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.util import joinURL
+from twext.web2.dav.util import parentForURL
+from twext.web2.http import HTTPError
 
 from twistedcaldav import caldavxml
 from twistedcaldav.caldavxml import caldav_namespace

Modified: CalendarServer/trunk/twistedcaldav/scheduling/ischedule.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/ischedule.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/scheduling/ischedule.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -21,14 +21,14 @@
 
 from twisted.python.failure import Failure
 
-from twisted.web2 import responsecode
-from twisted.web2.client.http import ClientRequest
-from twisted.web2.client.http import HTTPClientProtocol
-from twisted.web2.dav.util import davXMLFromStream, joinURL, allDataFromStream
-from twisted.web2.http import HTTPError
-from twisted.web2.http_headers import Headers
-from twisted.web2.http_headers import MimeType
-from twisted.web2.stream import MemoryStream
+from twext.web2 import responsecode
+from twext.web2.client.http import ClientRequest
+from twext.web2.client.http import HTTPClientProtocol
+from twext.web2.dav.util import davXMLFromStream, joinURL, allDataFromStream
+from twext.web2.http import HTTPError
+from twext.web2.http_headers import Headers
+from twext.web2.http_headers import MimeType
+from twext.web2.stream import MemoryStream
 
 from twext.log import Logger, logLevels
 from twext.internet.ssl import ChainingOpenSSLContextFactory

Modified: CalendarServer/trunk/twistedcaldav/scheduling/ischeduleservers.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/ischeduleservers.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/scheduling/ischeduleservers.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -14,7 +14,7 @@
 # limitations under the License.
 ##
 
-from twisted.python.filepath import FilePath
+from twext.python.filepath import CachingFilePath as FilePath
 
 from twext.log import Logger
 

Modified: CalendarServer/trunk/twistedcaldav/scheduling/processing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/processing.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/scheduling/processing.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -24,9 +24,9 @@
 
 from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2.dav.method.report import NumberOfMatchesWithinLimits
-from twisted.web2.dav.util import joinURL
-from twisted.web2.http import HTTPError
+from twext.web2.dav.method.report import NumberOfMatchesWithinLimits
+from twext.web2.dav.util import joinURL
+from twext.web2.http import HTTPError
 from twistedcaldav import customxml, caldavxml
 from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.ical import Property

Modified: CalendarServer/trunk/twistedcaldav/scheduling/scheduler.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/scheduler.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/scheduling/scheduler.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -19,11 +19,11 @@
 
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.python.failure import Failure
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.http import errorForFailure, messageForFailure, statusForFailure
-from twisted.web2.http import HTTPError, Response, StatusResponse
-from twisted.web2.http_headers import MimeType
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.http import errorForFailure, messageForFailure, statusForFailure
+from twext.web2.http import HTTPError, Response, StatusResponse
+from twext.web2.http_headers import MimeType
 
 from twistedcaldav import caldavxml, dateops
 from twistedcaldav.accounting import accountingEnabled, emitAccounting

Modified: CalendarServer/trunk/twistedcaldav/scheduling/test/test_imip.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/test/test_imip.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/scheduling/test/test_imip.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -15,7 +15,7 @@
 ##
 
 from twisted.internet.defer import inlineCallbacks
-from twisted.web2 import responsecode
+from twext.web2 import responsecode
 from twistedcaldav.ical import Component
 from twistedcaldav.scheduling.cuaddress import RemoteCalendarUser
 from twistedcaldav.scheduling.imip import ScheduleViaIMip

Modified: CalendarServer/trunk/twistedcaldav/scheduling/test/test_implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/test/test_implicit.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/scheduling/test/test_implicit.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -18,7 +18,7 @@
 import twistedcaldav.test.util
 from twistedcaldav.scheduling.implicit import ImplicitScheduler
 from dateutil.tz import tzutc
-from twisted.web2.dav import davxml
+from twext.web2.dav import davxml
 import datetime
 
 class Implicit (twistedcaldav.test.util.TestCase):

Modified: CalendarServer/trunk/twistedcaldav/static.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/static.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/static.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -49,17 +49,17 @@
 
 from twisted.internet.defer import fail, succeed, inlineCallbacks, returnValue, maybeDeferred
 from twisted.python.failure import Failure
-from twisted.python.filepath import FilePath
-from twisted.web2 import responsecode, http, http_headers
-from twisted.web2.http import HTTPError, StatusResponse
-from twisted.web2.dav import davxml
-from twisted.web2.dav.element.base import dav_namespace
-from twisted.web2.dav.fileop import mkcollection, rmdir
-from twisted.web2.dav.idav import IDAVResource
-from twisted.web2.dav.noneprops import NonePropertyStore
-from twisted.web2.dav.resource import AccessDeniedError
-from twisted.web2.dav.resource import davPrivilegeSet
-from twisted.web2.dav.util import parentForURL, bindMethods, joinURL
+from twext.python.filepath import CachingFilePath as FilePath
+from twext.web2 import responsecode, http, http_headers
+from twext.web2.http import HTTPError, StatusResponse
+from twext.web2.dav import davxml
+from twext.web2.dav.element.base import dav_namespace
+from twext.web2.dav.fileop import mkcollection, rmdir
+from twext.web2.dav.idav import IDAVResource
+from twext.web2.dav.noneprops import NonePropertyStore
+from twext.web2.dav.resource import AccessDeniedError
+from twext.web2.dav.resource import davPrivilegeSet
+from twext.web2.dav.util import parentForURL, bindMethods, joinURL
 
 from twistedcaldav import caldavxml
 from twistedcaldav import carddavxml
@@ -126,7 +126,7 @@
             if self.exists() and self.hasDeadProperty(TwistedScheduleMatchETags):
                 etags = self.readDeadProperty(TwistedScheduleMatchETags).children
                 if len(etags) > 1:
-                    # This is almost verbatim from twisted.web2.static.checkPreconditions
+                    # This is almost verbatim from twext.web2.static.checkPreconditions
                     if request.method not in ("GET", "HEAD"):
                         
                         # Loop over each tag and succeed if any one matches, else re-raise last exception

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -19,8 +19,8 @@
 import copy
 import re
 
-from twisted.web2.dav import davxml
-from twisted.web2.dav.resource import TwistedACLInheritable
+from twext.web2.dav import davxml
+from twext.web2.dav.resource import TwistedACLInheritable
 
 from twext.python.plistlib import PlistParser
 from twext.log import Logger, InvalidLogLevelError

Modified: CalendarServer/trunk/twistedcaldav/test/test_DAV.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_DAV.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_DAV.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -16,26 +16,26 @@
 
 from twistedcaldav.static import CalDAVFile as MyResource
 
-import twisted.web2.dav.test.test_acl
-import twisted.web2.dav.test.test_copy
-import twisted.web2.dav.test.test_delete
-import twisted.web2.dav.test.test_lock
-import twisted.web2.dav.test.test_mkcol
-import twisted.web2.dav.test.test_move
-import twisted.web2.dav.test.test_options
-import twisted.web2.dav.test.test_prop
-import twisted.web2.dav.test.test_put
-import twisted.web2.dav.test.test_report
-import twisted.web2.dav.test.test_report_expand
+import twext.web2.dav.test.test_acl
+import twext.web2.dav.test.test_copy
+import twext.web2.dav.test.test_delete
+import twext.web2.dav.test.test_lock
+import twext.web2.dav.test.test_mkcol
+import twext.web2.dav.test.test_move
+import twext.web2.dav.test.test_options
+import twext.web2.dav.test.test_prop
+import twext.web2.dav.test.test_put
+import twext.web2.dav.test.test_report
+import twext.web2.dav.test.test_report_expand
 
-class ACL           (twisted.web2.dav.test.test_acl.ACL                    ): resource_class = MyResource
-class COPY          (twisted.web2.dav.test.test_copy.COPY                  ): resource_class = MyResource
-class DELETE        (twisted.web2.dav.test.test_delete.DELETE              ): resource_class = MyResource
-class LOCK_UNLOCK   (twisted.web2.dav.test.test_lock.LOCK_UNLOCK           ): resource_class = MyResource
-class MKCOL         (twisted.web2.dav.test.test_mkcol.MKCOL                ): resource_class = MyResource
-class MOVE          (twisted.web2.dav.test.test_move.MOVE                  ): resource_class = MyResource
-class OPTIONS       (twisted.web2.dav.test.test_options.OPTIONS            ): resource_class = MyResource
-class PROP          (twisted.web2.dav.test.test_prop.PROP                  ): resource_class = MyResource
-class PUT           (twisted.web2.dav.test.test_put.PUT                    ): resource_class = MyResource
-class REPORT        (twisted.web2.dav.test.test_report.REPORT              ): resource_class = MyResource
-class REPORT_expand (twisted.web2.dav.test.test_report_expand.REPORT_expand): resource_class = MyResource
+class ACL           (twext.web2.dav.test.test_acl.ACL                    ): resource_class = MyResource
+class COPY          (twext.web2.dav.test.test_copy.COPY                  ): resource_class = MyResource
+class DELETE        (twext.web2.dav.test.test_delete.DELETE              ): resource_class = MyResource
+class LOCK_UNLOCK   (twext.web2.dav.test.test_lock.LOCK_UNLOCK           ): resource_class = MyResource
+class MKCOL         (twext.web2.dav.test.test_mkcol.MKCOL                ): resource_class = MyResource
+class MOVE          (twext.web2.dav.test.test_move.MOVE                  ): resource_class = MyResource
+class OPTIONS       (twext.web2.dav.test.test_options.OPTIONS            ): resource_class = MyResource
+class PROP          (twext.web2.dav.test.test_prop.PROP                  ): resource_class = MyResource
+class PUT           (twext.web2.dav.test.test_put.PUT                    ): resource_class = MyResource
+class REPORT        (twext.web2.dav.test.test_report.REPORT              ): resource_class = MyResource
+class REPORT_expand (twext.web2.dav.test.test_report_expand.REPORT_expand): resource_class = MyResource

Modified: CalendarServer/trunk/twistedcaldav/test/test_accounting.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_accounting.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_accounting.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -15,8 +15,8 @@
 ##
 
 from twext.web2.channel.http import HTTPLoggingChannelRequest
-from twisted.web2 import http_headers
-from twisted.web2.channel.http import HTTPChannel
+from twext.web2 import http_headers
+from twext.web2.channel.http import HTTPChannel
 from twistedcaldav.accounting import emitAccounting
 from twistedcaldav.config import config
 import twistedcaldav.test.util

Modified: CalendarServer/trunk/twistedcaldav/test/test_cache.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_cache.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_cache.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -20,10 +20,10 @@
 
 from twisted.internet.defer import succeed, maybeDeferred
 
-from twisted.web2.dav import davxml
-from twisted.web2.dav.util import allDataFromStream
-from twisted.web2.stream import MemoryStream
-from twisted.web2.http_headers import Headers
+from twext.web2.dav import davxml
+from twext.web2.dav.util import allDataFromStream
+from twext.web2.stream import MemoryStream
+from twext.web2.http_headers import Headers
 
 from twistedcaldav.cache import MemcacheResponseCache
 from twistedcaldav.cache import MemcacheChangeNotifier

Modified: CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -18,13 +18,13 @@
 import shutil
 
 from twisted.trial.unittest import SkipTest
-from twisted.web2 import responsecode
-from twisted.web2.iweb import IResponse
-from twisted.web2.stream import MemoryStream
-from twisted.web2.dav import davxml
-from twisted.web2.dav.fileop import rmdir
-from twisted.web2.dav.util import davXMLFromStream
-from twisted.web2.test.test_server import SimpleRequest
+from twext.web2 import responsecode
+from twext.web2.iweb import IResponse
+from twext.web2.stream import MemoryStream
+from twext.web2.dav import davxml
+from twext.web2.dav.fileop import rmdir
+from twext.web2.dav.util import davXMLFromStream
+from twext.web2.test.test_server import SimpleRequest
 
 import twistedcaldav.test.util
 from twistedcaldav import caldavxml

Modified: CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -17,11 +17,11 @@
 import os
 
 from twisted.internet.defer import DeferredList
-from twisted.web2 import responsecode
-from twisted.web2.iweb import IResponse
-from twisted.web2.stream import MemoryStream, FileStream
-from twisted.web2.http_headers import MimeType
-from twisted.web2.test.test_server import SimpleRequest
+from twext.web2 import responsecode
+from twext.web2.iweb import IResponse
+from twext.web2.stream import MemoryStream, FileStream
+from twext.web2.http_headers import MimeType
+from twext.web2.test.test_server import SimpleRequest
 
 from twistedcaldav.ical import Component
 from twistedcaldav.memcachelock import MemcacheLock

Modified: CalendarServer/trunk/twistedcaldav/test/test_extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_extensions.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_extensions.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -20,14 +20,14 @@
 from twisted.trial.unittest import TestCase
 
 from twisted.internet.defer import inlineCallbacks
-from twisted.python.filepath import FilePath
+from twext.python.filepath import CachingFilePath as FilePath
 
 from twisted.web.microdom import parseString
-from twisted.web2.static import MetaDataMixin
+from twext.web2.static import MetaDataMixin
 
 from twistedcaldav.extensions import DAVFile
 
-from twisted.web2.dav.element.base import WebDAVElement
+from twext.web2.dav.element.base import WebDAVElement
 
 class UnicodeProperty(WebDAVElement):
     """

Modified: CalendarServer/trunk/twistedcaldav/test/test_freebusyquery.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_freebusyquery.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_freebusyquery.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -20,11 +20,11 @@
 import os
 import shutil
 
-from twisted.web2 import responsecode
-from twisted.web2.iweb import IResponse
-from twisted.web2.stream import MemoryStream
-from twisted.web2.dav.fileop import rmdir
-from twisted.web2.test.test_server import SimpleRequest
+from twext.web2 import responsecode
+from twext.web2.iweb import IResponse
+from twext.web2.stream import MemoryStream
+from twext.web2.dav.fileop import rmdir
+from twext.web2.test.test_server import SimpleRequest
 
 import twistedcaldav.test.util
 from twistedcaldav import caldavxml

Modified: CalendarServer/trunk/twistedcaldav/test/test_kerberos.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_kerberos.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_kerberos.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -17,7 +17,7 @@
 from twisted.cred.error import LoginFailed
 from twisted.cred.error import UnauthorizedLogin
 from twisted.internet.defer import inlineCallbacks
-from twisted.web2.test.test_server import SimpleRequest
+from twext.web2.test.test_server import SimpleRequest
 
 from twistedcaldav import authkerb
 import twistedcaldav.test.util

Modified: CalendarServer/trunk/twistedcaldav/test/test_log.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_log.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_log.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -157,29 +157,29 @@
         """
         Setting and retrieving log levels.
         """
-        setLogLevelForNamespace("twisted.web2", "debug")
-        setLogLevelForNamespace("twisted.web2.dav", "error")
+        setLogLevelForNamespace("twext.web2", "debug")
+        setLogLevelForNamespace("twext.web2.dav", "error")
 
         self.assertEquals(logLevelForNamespace("twisted"                     ), defaultLogLevel)
-        self.assertEquals(logLevelForNamespace("twisted.web2"                ), "debug")
-        self.assertEquals(logLevelForNamespace("twisted.web2.dav"            ), "error")
-        self.assertEquals(logLevelForNamespace("twisted.web2.dav.test"       ), "error")
-        self.assertEquals(logLevelForNamespace("twisted.web2.dav.test1.test2"), "error")
+        self.assertEquals(logLevelForNamespace("twext.web2"                ), "debug")
+        self.assertEquals(logLevelForNamespace("twext.web2.dav"            ), "error")
+        self.assertEquals(logLevelForNamespace("twext.web2.dav.test"       ), "error")
+        self.assertEquals(logLevelForNamespace("twext.web2.dav.test1.test2"), "error")
 
     def test_clearLogLevel(self):
         """
         Clearing log levels.
         """
-        setLogLevelForNamespace("twisted.web2", "debug")
-        setLogLevelForNamespace("twisted.web2.dav", "error")
+        setLogLevelForNamespace("twext.web2", "debug")
+        setLogLevelForNamespace("twext.web2.dav", "error")
 
         clearLogLevels()
 
         self.assertEquals(logLevelForNamespace("twisted"                     ), defaultLogLevel)
-        self.assertEquals(logLevelForNamespace("twisted.web2"                ), defaultLogLevel)
-        self.assertEquals(logLevelForNamespace("twisted.web2.dav"            ), defaultLogLevel)
-        self.assertEquals(logLevelForNamespace("twisted.web2.dav.test"       ), defaultLogLevel)
-        self.assertEquals(logLevelForNamespace("twisted.web2.dav.test1.test2"), defaultLogLevel)
+        self.assertEquals(logLevelForNamespace("twext.web2"                ), defaultLogLevel)
+        self.assertEquals(logLevelForNamespace("twext.web2.dav"            ), defaultLogLevel)
+        self.assertEquals(logLevelForNamespace("twext.web2.dav.test"       ), defaultLogLevel)
+        self.assertEquals(logLevelForNamespace("twext.web2.dav.test1.test2"), defaultLogLevel)
 
     def test_willLogAtLevel(self):
         """

Modified: CalendarServer/trunk/twistedcaldav/test/test_memcacheprops.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_memcacheprops.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_memcacheprops.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -26,7 +26,7 @@
 
 import os
 
-from twisted.web2.http import HTTPError
+from twext.web2.http import HTTPError
 
 from twistedcaldav.memcacheprops import MemcachePropertyCollection
 from twistedcaldav.test.util import InMemoryPropertyStore

Modified: CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -18,12 +18,12 @@
 
 import os
 
-from twisted.web2 import responsecode
-from twisted.web2.iweb import IResponse
-from twisted.web2.stream import MemoryStream
-from twisted.web2.dav import davxml
-from twisted.web2.dav.fileop import rmdir
-from twisted.web2.test.test_server import SimpleRequest
+from twext.web2 import responsecode
+from twext.web2.iweb import IResponse
+from twext.web2.stream import MemoryStream
+from twext.web2.dav import davxml
+from twext.web2.dav.fileop import rmdir
+from twext.web2.test.test_server import SimpleRequest
 
 import twistedcaldav.test.util
 from twistedcaldav import caldavxml

Modified: CalendarServer/trunk/twistedcaldav/test/test_multiget.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_multiget.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_multiget.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -18,13 +18,13 @@
 import shutil
 
 from twisted.internet.defer import inlineCallbacks
-from twisted.web2 import responsecode
-from twisted.web2.iweb import IResponse
-from twisted.web2.stream import MemoryStream
-from twisted.web2.dav import davxml
-from twisted.web2.dav.fileop import rmdir
-from twisted.web2.dav.util import davXMLFromStream
-from twisted.web2.test.test_server import SimpleRequest
+from twext.web2 import responsecode
+from twext.web2.iweb import IResponse
+from twext.web2.stream import MemoryStream
+from twext.web2.dav import davxml
+from twext.web2.dav.fileop import rmdir
+from twext.web2.dav.util import davXMLFromStream
+from twext.web2.test.test_server import SimpleRequest
 
 import twistedcaldav.test.util
 from twistedcaldav import caldavxml

Modified: CalendarServer/trunk/twistedcaldav/test/test_options.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_options.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_options.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -14,8 +14,8 @@
 # limitations under the License.
 ##
 
-from twisted.web2.iweb import IResponse
-from twisted.web2.test.test_server import SimpleRequest
+from twext.web2.iweb import IResponse
+from twext.web2.test.test_server import SimpleRequest
 
 import twistedcaldav.test.util
 from twistedcaldav.config import config

Modified: CalendarServer/trunk/twistedcaldav/test/test_props.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_props.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_props.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -14,15 +14,15 @@
 # limitations under the License.
 ##
 
-from twisted.web2.stream import MemoryStream
+from twext.web2.stream import MemoryStream
 
 import os
 
-from twisted.web2 import responsecode
-from twisted.web2.iweb import IResponse
-from twisted.web2.dav import davxml
-from twisted.web2.dav.util import davXMLFromStream
-from twisted.web2.test.test_server import SimpleRequest
+from twext.web2 import responsecode
+from twext.web2.iweb import IResponse
+from twext.web2.dav import davxml
+from twext.web2.dav.util import davXMLFromStream
+from twext.web2.test.test_server import SimpleRequest
 from twistedcaldav import caldavxml
 
 import twistedcaldav.test.util

Modified: CalendarServer/trunk/twistedcaldav/test/test_schedule.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_schedule.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_schedule.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -16,12 +16,12 @@
 
 import os
 
-from twisted.web2 import responsecode
-from twisted.web2.iweb import IResponse
-from twisted.web2.dav import davxml
-from twisted.web2.dav.util import davXMLFromStream
-from twisted.web2.stream import MemoryStream
-from twisted.web2.test.test_server import SimpleRequest
+from twext.web2 import responsecode
+from twext.web2.iweb import IResponse
+from twext.web2.dav import davxml
+from twext.web2.dav.util import davXMLFromStream
+from twext.web2.stream import MemoryStream
+from twext.web2.test.test_server import SimpleRequest
 
 from twistedcaldav import caldavxml
 from twistedcaldav.static import ScheduleInboxFile

Modified: CalendarServer/trunk/twistedcaldav/test/test_stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_stdconfig.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_stdconfig.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -17,7 +17,7 @@
 
 from cStringIO import StringIO
 
-from twisted.python.filepath import FilePath
+from twext.python.filepath import CachingFilePath as FilePath
 from twisted.trial.unittest import TestCase
 
 from twistedcaldav.config import Config

Modified: CalendarServer/trunk/twistedcaldav/test/test_upgrade.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_upgrade.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_upgrade.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -15,9 +15,10 @@
 ##
 
 
-from twisted.web2.dav import davxml
+from twext.web2.dav import davxml
 
 from twistedcaldav.config import config
+# from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyDatabase
 #from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyDatabase
 CalendarUserProxyDatabase = None
 from twistedcaldav.directory.xmlfile import XMLDirectoryService

Modified: CalendarServer/trunk/twistedcaldav/test/test_validation.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_validation.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/test_validation.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -21,8 +21,8 @@
 
 # XXX this should be public, but it isn't, since it's in a test_* module.  Need
 # to address this to use system twisted.
-from twisted.web2.test.test_server import SimpleRequest
-from twisted.web2.http import HTTPError
+from twext.web2.test.test_server import SimpleRequest
+from twext.web2.http import HTTPError
 
 from twistedcaldav.static import CalDAVFile
 from twistedcaldav.config import config

Modified: CalendarServer/trunk/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/util.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/test/util.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -21,7 +21,7 @@
 
 from twisted.python.failure import Failure
 from twisted.internet.defer import succeed, fail
-from twisted.web2.http import HTTPError, StatusResponse
+from twext.web2.http import HTTPError, StatusResponse
 from twisted.internet.error import ProcessDone
 from twisted.internet.protocol import ProcessProtocol
 
@@ -31,12 +31,12 @@
 from twistedcaldav.static import CalDAVFile
 import memcacheclient
 
-import twisted.web2.dav.test.util
+import twext.web2.dav.test.util
 
 from twisted.internet.base import DelayedCall
 DelayedCall.debug = True
 
-class TestCase(twisted.web2.dav.test.util.TestCase):
+class TestCase(twext.web2.dav.test.util.TestCase):
     resource_class = CalDAVFile
 
     def setUp(self):

Modified: CalendarServer/trunk/twistedcaldav/timezoneservice.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/timezoneservice.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/timezoneservice.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -24,12 +24,12 @@
 
 from twext.web2.dav.davxml import ErrorResponse
 
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.http import HTTPError
-from twisted.web2.http import Response
-from twisted.web2.http_headers import MimeType
-from twisted.web2.stream import MemoryStream
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.http import HTTPError
+from twext.web2.http import Response
+from twext.web2.http_headers import MimeType
+from twext.web2.stream import MemoryStream
 
 from twext.web2.http import XMLResponse
 

Modified: CalendarServer/trunk/twistedcaldav/upgrade.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/upgrade.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/upgrade.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -21,8 +21,8 @@
 from zlib import compress
 from cPickle import loads as unpickle, UnpicklingError
 
-from twisted.web2.dav.fileop import rmdir
-from twisted.web2.dav import davxml
+from twext.web2.dav.fileop import rmdir
+from twext.web2.dav import davxml
 
 from twext.log import Logger
 

Modified: CalendarServer/trunk/twistedcaldav/vcard.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/vcard.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/twistedcaldav/vcard.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -30,8 +30,8 @@
 from vobject.base import ContentLine as vContentLine
 from vobject.base import ParseError as vParseError
 
-from twisted.web2.stream import IStream
-from twisted.web2.dav.util import allDataFromStream
+from twext.web2.stream import IStream
+from twext.web2.dav.util import allDataFromStream
 
 vCardProductID = "-//CALENDARSERVER.ORG//NONSGML Version 1//EN"
 

Modified: CalendarServer/trunk/txcaldav/calendarstore/file.py
===================================================================
--- CalendarServer/trunk/txcaldav/calendarstore/file.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/txcaldav/calendarstore/file.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -29,7 +29,7 @@
 
 from zope.interface import implements
 
-from twisted.python.filepath import FilePath
+from twext.python.filepath import CachingFilePath as FilePath
 from twisted.internet.defer import inlineCallbacks
 
 from twext.log import LoggingMixIn

Modified: CalendarServer/trunk/txcaldav/calendarstore/test/test_file.py
===================================================================
--- CalendarServer/trunk/txcaldav/calendarstore/test/test_file.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/txcaldav/calendarstore/test/test_file.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -20,7 +20,7 @@
 
 from zope.interface.verify import verifyObject, BrokenMethodImplementation
 
-from twisted.python.filepath import FilePath
+from twext.python.filepath import CachingFilePath as FilePath
 from twisted.trial import unittest
 
 from twext.python.icalendar import Component as iComponent

Modified: CalendarServer/trunk/txdav/propertystore/test/test_xattr.py
===================================================================
--- CalendarServer/trunk/txdav/propertystore/test/test_xattr.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/txdav/propertystore/test/test_xattr.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -20,7 +20,7 @@
 
 from zope.interface.verify import verifyObject, BrokenMethodImplementation
 
-#from twisted.python.filepath import FilePath
+#from twext.python.filepath import CachingFilePath as FilePath
 from twisted.trial import unittest
 
 from txdav.idav import IPropertyStore

Modified: CalendarServer/trunk/txdav/propertystore/xattr.py
===================================================================
--- CalendarServer/trunk/txdav/propertystore/xattr.py	2010-02-17 22:24:12 UTC (rev 5149)
+++ CalendarServer/trunk/txdav/propertystore/xattr.py	2010-02-18 00:11:20 UTC (rev 5150)
@@ -35,7 +35,7 @@
 if getattr(xattr, "xattr", None) is None:
     raise ImportError("wrong xattr package imported")
 
-from twisted.web2.dav.davxml import WebDAVDocument
+from twext.web2.dav.davxml import WebDAVDocument
 
 from txdav.propertystore.base import AbstractPropertyStore, PropertyName
 from txdav.idav import PropertyStoreError
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100217/4139a22a/attachment-0001.html>


More information about the calendarserver-changes mailing list