[CalendarServer-changes] [2248] CalDAVClientLibrary/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Tue Mar 25 11:56:22 PDT 2008
Revision: 2248
http://trac.macosforge.org/projects/calendarserver/changeset/2248
Author: cdaboo at apple.com
Date: 2008-03-25 11:56:21 -0700 (Tue, 25 Mar 2008)
Log Message:
-----------
Initial import.
Added Paths:
-----------
CalDAVClientLibrary/trunk/.project
CalDAVClientLibrary/trunk/.pydevproject
CalDAVClientLibrary/trunk/LICENSE
CalDAVClientLibrary/trunk/README
CalDAVClientLibrary/trunk/runadmin.py
CalDAVClientLibrary/trunk/runshell.py
CalDAVClientLibrary/trunk/setup.py
CalDAVClientLibrary/trunk/src/
CalDAVClientLibrary/trunk/src/__init__.py
CalDAVClientLibrary/trunk/src/admin/
CalDAVClientLibrary/trunk/src/admin/__init__.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/__init__.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/__init__.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/addrecord.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/changepassword.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/command.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/listrecords.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/removerecord.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/directory.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/manage.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/record.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/recordtypes.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tags.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/__init__.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/test_directory.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/test_record.py
CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/utils.py
CalDAVClientLibrary/trunk/src/browser/
CalDAVClientLibrary/trunk/src/browser/__init__.py
CalDAVClientLibrary/trunk/src/browser/baseshell.py
CalDAVClientLibrary/trunk/src/browser/command.py
CalDAVClientLibrary/trunk/src/browser/commands/
CalDAVClientLibrary/trunk/src/browser/commands/__init__.py
CalDAVClientLibrary/trunk/src/browser/commands/acl.py
CalDAVClientLibrary/trunk/src/browser/commands/cat.py
CalDAVClientLibrary/trunk/src/browser/commands/cd.py
CalDAVClientLibrary/trunk/src/browser/commands/help.py
CalDAVClientLibrary/trunk/src/browser/commands/history.py
CalDAVClientLibrary/trunk/src/browser/commands/logging.py
CalDAVClientLibrary/trunk/src/browser/commands/ls.py
CalDAVClientLibrary/trunk/src/browser/commands/principal.py
CalDAVClientLibrary/trunk/src/browser/commands/props.py
CalDAVClientLibrary/trunk/src/browser/commands/proxies.py
CalDAVClientLibrary/trunk/src/browser/commands/quit.py
CalDAVClientLibrary/trunk/src/browser/commands/server.py
CalDAVClientLibrary/trunk/src/browser/commands/whoami.py
CalDAVClientLibrary/trunk/src/browser/shell.py
CalDAVClientLibrary/trunk/src/browser/subshell.py
CalDAVClientLibrary/trunk/src/browser/utils.py
CalDAVClientLibrary/trunk/src/client/
CalDAVClientLibrary/trunk/src/client/__init__.py
CalDAVClientLibrary/trunk/src/client/account.py
CalDAVClientLibrary/trunk/src/client/calendar.py
CalDAVClientLibrary/trunk/src/client/calendaruseraddress.py
CalDAVClientLibrary/trunk/src/client/clientsession.py
CalDAVClientLibrary/trunk/src/client/fullclient.py
CalDAVClientLibrary/trunk/src/client/principal.py
CalDAVClientLibrary/trunk/src/client/simple.py
CalDAVClientLibrary/trunk/src/protocol/
CalDAVClientLibrary/trunk/src/protocol/__init__.py
CalDAVClientLibrary/trunk/src/protocol/caldav/
CalDAVClientLibrary/trunk/src/protocol/caldav/__init__.py
CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/
CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/__init__.py
CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/caldavxml.py
CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/headers.py
CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/methods.py
CalDAVClientLibrary/trunk/src/protocol/caldav/makecalendar.py
CalDAVClientLibrary/trunk/src/protocol/caldav/multiget.py
CalDAVClientLibrary/trunk/src/protocol/caldav/tests/
CalDAVClientLibrary/trunk/src/protocol/caldav/tests/__init__.py
CalDAVClientLibrary/trunk/src/protocol/caldav/tests/test_makecalendar.py
CalDAVClientLibrary/trunk/src/protocol/caldav/tests/test_multiget.py
CalDAVClientLibrary/trunk/src/protocol/http/
CalDAVClientLibrary/trunk/src/protocol/http/__init__.py
CalDAVClientLibrary/trunk/src/protocol/http/authentication/
CalDAVClientLibrary/trunk/src/protocol/http/authentication/__init__.py
CalDAVClientLibrary/trunk/src/protocol/http/authentication/authenticator.py
CalDAVClientLibrary/trunk/src/protocol/http/authentication/basic.py
CalDAVClientLibrary/trunk/src/protocol/http/authentication/digest.py
CalDAVClientLibrary/trunk/src/protocol/http/authentication/tests/
CalDAVClientLibrary/trunk/src/protocol/http/authentication/tests/__init__.py
CalDAVClientLibrary/trunk/src/protocol/http/authentication/tests/test_basic.py
CalDAVClientLibrary/trunk/src/protocol/http/data/
CalDAVClientLibrary/trunk/src/protocol/http/data/__init__.py
CalDAVClientLibrary/trunk/src/protocol/http/data/data.py
CalDAVClientLibrary/trunk/src/protocol/http/data/file.py
CalDAVClientLibrary/trunk/src/protocol/http/data/string.py
CalDAVClientLibrary/trunk/src/protocol/http/definitions/
CalDAVClientLibrary/trunk/src/protocol/http/definitions/__init__.py
CalDAVClientLibrary/trunk/src/protocol/http/definitions/headers.py
CalDAVClientLibrary/trunk/src/protocol/http/definitions/methods.py
CalDAVClientLibrary/trunk/src/protocol/http/definitions/statuscodes.py
CalDAVClientLibrary/trunk/src/protocol/http/requestresponse.py
CalDAVClientLibrary/trunk/src/protocol/http/session.py
CalDAVClientLibrary/trunk/src/protocol/http/tests/
CalDAVClientLibrary/trunk/src/protocol/http/tests/__init__.py
CalDAVClientLibrary/trunk/src/protocol/http/tests/test_requestresponse.py
CalDAVClientLibrary/trunk/src/protocol/http/tests/test_util.py
CalDAVClientLibrary/trunk/src/protocol/http/util.py
CalDAVClientLibrary/trunk/src/protocol/tests/
CalDAVClientLibrary/trunk/src/protocol/tests/__init__.py
CalDAVClientLibrary/trunk/src/protocol/tests/test_url.py
CalDAVClientLibrary/trunk/src/protocol/url.py
CalDAVClientLibrary/trunk/src/protocol/utils/
CalDAVClientLibrary/trunk/src/protocol/utils/__init__.py
CalDAVClientLibrary/trunk/src/protocol/utils/xmlhelpers.py
CalDAVClientLibrary/trunk/src/protocol/webdav/
CalDAVClientLibrary/trunk/src/protocol/webdav/__init__.py
CalDAVClientLibrary/trunk/src/protocol/webdav/ace.py
CalDAVClientLibrary/trunk/src/protocol/webdav/acl.py
CalDAVClientLibrary/trunk/src/protocol/webdav/copy.py
CalDAVClientLibrary/trunk/src/protocol/webdav/copymovebase.py
CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/
CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/__init__.py
CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/davxml.py
CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/headers.py
CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/methods.py
CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/statuscodes.py
CalDAVClientLibrary/trunk/src/protocol/webdav/delete.py
CalDAVClientLibrary/trunk/src/protocol/webdav/get.py
CalDAVClientLibrary/trunk/src/protocol/webdav/getbase.py
CalDAVClientLibrary/trunk/src/protocol/webdav/head.py
CalDAVClientLibrary/trunk/src/protocol/webdav/lock.py
CalDAVClientLibrary/trunk/src/protocol/webdav/makecollection.py
CalDAVClientLibrary/trunk/src/protocol/webdav/move.py
CalDAVClientLibrary/trunk/src/protocol/webdav/multiresponseparser.py
CalDAVClientLibrary/trunk/src/protocol/webdav/options.py
CalDAVClientLibrary/trunk/src/protocol/webdav/principalmatch.py
CalDAVClientLibrary/trunk/src/protocol/webdav/propall.py
CalDAVClientLibrary/trunk/src/protocol/webdav/propfind.py
CalDAVClientLibrary/trunk/src/protocol/webdav/propfindbase.py
CalDAVClientLibrary/trunk/src/protocol/webdav/propfindparser.py
CalDAVClientLibrary/trunk/src/protocol/webdav/propnames.py
CalDAVClientLibrary/trunk/src/protocol/webdav/proppatch.py
CalDAVClientLibrary/trunk/src/protocol/webdav/put.py
CalDAVClientLibrary/trunk/src/protocol/webdav/report.py
CalDAVClientLibrary/trunk/src/protocol/webdav/requestresponse.py
CalDAVClientLibrary/trunk/src/protocol/webdav/session.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/__init__.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_ace.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_acl.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_copy.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_delete.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_get.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_head.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_lock.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_move.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_options.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_principalmatch.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_propfind.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_propfindparser.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_propnames.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_put.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_report.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_template.py
CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_unlock.py
CalDAVClientLibrary/trunk/src/protocol/webdav/unlock.py
CalDAVClientLibrary/trunk/src/protocol/webdav/xmlresponseparser.py
CalDAVClientLibrary/trunk/src/ui/
CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.nib/
CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.nib/classes.nib
CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.nib/info.nib
CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.nib/keyedobjects.nib
CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.py
CalDAVClientLibrary/trunk/src/ui/__init__.py
CalDAVClientLibrary/trunk/src/ui/resource.py
CalDAVClientLibrary/trunk/src/ui/session.py
Added: CalDAVClientLibrary/trunk/.project
===================================================================
--- CalDAVClientLibrary/trunk/.project (rev 0)
+++ CalDAVClientLibrary/trunk/.project 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>CalDAVClientLibrary</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.python.pydev.PyDevBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.python.pydev.pythonNature</nature>
+ </natures>
+</projectDescription>
Added: CalDAVClientLibrary/trunk/.pydevproject
===================================================================
--- CalDAVClientLibrary/trunk/.pydevproject (rev 0)
+++ CalDAVClientLibrary/trunk/.pydevproject 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse-pydev version="1.0"?>
+
+<pydev_project>
+<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
+<path>/CalDAVClientLibrary/src</path>
+</pydev_pathproperty>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.5</pydev_property>
+<pydev_pathproperty name="org.python.pydev.PROJECT_EXTERNAL_SOURCE_PATH">
+<path>/Volumes/Data/Users/cyrusdaboo/Documents/Development/Apple/eclipse/PyCalendar/src</path>
+<path>/System/Library/Frameworks/Python.framework/Versions/2.5/Extras/lib/python/PyObjC</path>
+</pydev_pathproperty>
+</pydev_project>
Added: CalDAVClientLibrary/trunk/LICENSE
===================================================================
--- CalDAVClientLibrary/trunk/LICENSE (rev 0)
+++ CalDAVClientLibrary/trunk/LICENSE 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
Added: CalDAVClientLibrary/trunk/README
===================================================================
--- CalDAVClientLibrary/trunk/README (rev 0)
+++ CalDAVClientLibrary/trunk/README 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,105 @@
+README for CalDAVClientLibrary
+
+INTRODUCTION
+
+CalDAVCLientLibrary is a Python library and tool for CalDAV. It is
+comprised of four main modules:
+
+protocol: this implements an HTTP/WebDAV/CalDAV protocol stack, using
+httplib to communicate with the server.
+
+client: this implements a CalDAV client session, with higher level
+functionality than the protocol module (e.g. 'get properties on resource
+X and return as a dict'). There is a higher level abstraction using an
+object model to repesent a session, accounts, principals and calendars
+as objects.
+
+browser: this implements a shell-like browser that lets you interact
+with the CalDAV server directly via protocol. You can 'cd' to different
+parts of the repository, 'ls' to list a collection, 'cat' to read
+resource data, 'props' to get properties. Then there are some higher
+level functions such as 'proxies' which let you manage (read and edit)
+the proxy list for a principal, and 'acl' which lets you manage ACLs
+directly on resources. For those, the tool takes care of mapping from
+principal paths to principal URLs etc. Help is provided for each command
+(type '?'). It is easily extensible by adding new commands.
+
+ui: a PyObjC application with a WebDAV browser GUI. This provides a file
+system like browser that allows properties and the data for a selected
+WebDAV resource do be displayed.
+
+admin: a user account administration tool. Currently works only with the
+XML file directory account.
+
+*** NB This package requires Python 2.5. ***
+
+The runshell.py script will launch the command line browser shell.
+The runadmin.py script will run the XML directory admin tool.
+
+
+SHELL TOOL
+
+-- COMMAND LINE OPTIONS
+
+ Usage: runshell [OPTIONS]
+
+ Options:
+
+ -l start with HTTP logging on.
+
+ --server=HOST url of the server include http/https scheme and
+ port [REQUIRED].
+
+ --user=USER user name to login as - will be prompted if not
+ present [OPTIONAL].
+
+ --pswd=PSWD password for user - will be prompted if not
+ present [OPTIONAL].
+
+-- QUICKSTART - COMMANDLINE
+
+To browse a calendar server on the local machine:
+
+ ./runshell.py --server http://localhost:8008
+
+or, for SSL:
+
+ ./runshell.py --server https://localhost:8443
+
+Then type '?' followed by return to see the list of available commands.
+
+
+UI TOOL
+
+-- QUICKSTART - GUI
+
+Build the GUI app using:
+
+ python setup.py py2app
+
+The application will be placed in the 'dist' directory. Double-click
+that to launch it. One it it running, click the 'Server' toolbar button
+and specify a server, user id and password.
+
+The app will then display the top-level of the server resource hierarchy
+in the browser pane on the left. You can click and navigate through the
+resources via that pane (the 'Browser' toolbar buttons determine whether
+the browser uses a column or list view).
+
+When a resource is selected in the browser pane, its properties or data
+are display in the right hand pane. You can toggle between viewing
+properties or data by using the 'View' toolbar buttons.
+
+
+ADMIN TOOL
+
+-- QUICKSTART - COMMANDLINE
+
+To run the tool and see the list of available commands:
+
+ ./runadmin.py --help
+
+
+TO DO
+
+Lots of error handling and documentation.
Added: CalDAVClientLibrary/trunk/runadmin.py
===================================================================
--- CalDAVClientLibrary/trunk/runadmin.py (rev 0)
+++ CalDAVClientLibrary/trunk/runadmin.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2007-2008 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.
+##
+
+#
+# Runs the CalDAVTester test suite ensuring that required packages are available.
+#
+
+if __name__ == "__main__":
+
+ import os
+ import sys
+
+ cwd = os.getcwd()
+ sys.path.append(os.path.join(cwd, "src"))
+
+ from admin.xmlaccounts import manage
+ manage.runit()
Property changes on: CalDAVClientLibrary/trunk/runadmin.py
___________________________________________________________________
Name: svn:executable
+ *
Added: CalDAVClientLibrary/trunk/runshell.py
===================================================================
--- CalDAVClientLibrary/trunk/runshell.py (rev 0)
+++ CalDAVClientLibrary/trunk/runshell.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2007-2008 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.
+##
+
+#
+# Runs the CalDAVTester test suite ensuring that required packages are available.
+#
+
+if __name__ == "__main__":
+
+ import os
+ import sys
+
+ cwd = os.getcwd()
+ sys.path.append(os.path.join(cwd, "src"))
+
+ from src.browser import shell
+ shell.runit()
Property changes on: CalDAVClientLibrary/trunk/runshell.py
___________________________________________________________________
Name: svn:executable
+ *
Added: CalDAVClientLibrary/trunk/setup.py
===================================================================
--- CalDAVClientLibrary/trunk/setup.py (rev 0)
+++ CalDAVClientLibrary/trunk/setup.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,32 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+"""
+Script for building the UI app (OS X only).
+
+Usage:
+ python setup.py py2app
+"""
+
+from distutils.core import setup
+import py2app
+
+plist = dict(NSMainNibFile="WebDAVBrowser")
+setup(
+ app=["src/ui/WebDAVBrowser.py"],
+ data_files=["src/ui/WebDAVBrowser.nib", ],
+ options=dict(py2app=dict(plist=plist, includes=["urllib", "sha", "md5",], packages=["src/client", "src/protocol", "src/ui",])),
+)
Added: CalDAVClientLibrary/trunk/src/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2007-2008 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.
+##
Added: CalDAVClientLibrary/trunk/src/admin/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,16 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,16 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,28 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from admin.xmlaccounts.commands.addrecord import AddRecord
+from admin.xmlaccounts.commands.changepassword import ChangePassword
+from admin.xmlaccounts.commands.listrecords import ListRecords
+from admin.xmlaccounts.commands.removerecord import RemoveRecord
+
+# Commands register themselves in this dict
+registered = {}
+
+registered[AddRecord.CMDNAME] = AddRecord
+registered[ChangePassword.CMDNAME] = ChangePassword
+registered[ListRecords.CMDNAME] = ListRecords
+registered[RemoveRecord.CMDNAME] = RemoveRecord
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/addrecord.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/addrecord.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/addrecord.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,102 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from admin.xmlaccounts.commands.command import Command
+from admin.xmlaccounts.record import XMLRecord
+from uuid import uuid4
+from admin.xmlaccounts import recordtypes
+
+class AddRecord(Command):
+
+ CMDNAME = "add"
+
+ def __init__(self):
+ super(AddRecord, self).__init__(self.CMDNAME, "Add a record of the specified type.")
+
+ def doCommand(self):
+ if self.doAdd():
+ return self.writeAccounts()
+ return 0
+
+ def doAdd(self):
+
+ # Prompt for each thing we need in the record
+ record = XMLRecord()
+ record.recordType = self.recordType
+ print "Enter the fields for the record (ctrl-D to stop at any time)"
+ try:
+ # uid
+ while True:
+ record.uid = raw_input("Id: ")
+ if not record.uid:
+ print "A valid uid is required. Please try again."
+ elif self.directory.containsRecord(self.recordType, record.uid):
+ print "Record uid: '%s' of type: '%s' does already exists in the directory." % (record.uid, self.recordType,)
+ else:
+ break
+
+ # guid
+ while True:
+ record.guid = raw_input("GUID [leave empty to auto-generate]: ")
+ if record.guid and self.directory.containsGUID(record.guid):
+ print "GUID: '%s' already used in the directory" % (record.guid,)
+ else:
+ break
+
+ # password
+ record.password = self.promptPassword()
+
+ # name
+ record.name = raw_input("Name: ")
+
+ # members
+ if self.recordType in (recordtypes.recordType_groups,):
+ record.members = self.getMemberList("Enter members of this group", "Member", "members")
+
+ # cuaddr
+ while True:
+ cuaddr = raw_input("Calendar user address [leave empty to stop adding addresses]: ")
+ if cuaddr:
+ record.calendarUserAddresses.add(cuaddr)
+ else:
+ break
+
+ # auto-schedule
+ if self.recordType in (recordtypes.recordType_locations, recordtypes.recordType_resources,):
+ auto_schedule = raw_input("Turn on automatic scheduling [y/n]?: ")
+ if auto_schedule == "y":
+ record.autoSchedule = True
+
+ # enabled for calendaring
+ if self.recordType in (recordtypes.recordType_users, recordtypes.recordType_groups,):
+ enable_calendars = raw_input("Create calendar account rather than access-only account [y/n]?: ")
+ if enable_calendars == "n":
+ record.enabledForCalendaring = False
+
+ # proxies
+ if self.recordType in (recordtypes.recordType_locations, recordtypes.recordType_resources,):
+ record.proxies = self.getMemberList("Enter proxies of this location or resource", "Proxy", "proxies")
+
+ except EOFError:
+ return 0
+
+ # Now validate the record and save it
+ if not record.guid:
+ record.guid = str(uuid4())
+
+ self.directory.addRecord(record)
+
+ return 1
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/changepassword.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/changepassword.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/changepassword.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,93 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from admin.xmlaccounts.commands.command import Command
+import getopt
+
+class ChangePassword(Command):
+
+ CMDNAME = "passwd"
+
+ def __init__(self):
+ super(ChangePassword, self).__init__(self.CMDNAME, "Change the password for a record.")
+ self.uid = None
+
+ def usage(self):
+ print """USAGE: %s TYPE [OPTIONS]
+
+TYPE: One of "users", "groups", "locations" or "resources". Also,
+"u", "g", "l" or "r" as shortcuts.
+
+Options:
+ -f file path to accounts.xml
+ --uid UID of record to change
+""" % (self.cmdname,)
+
+ def execute(self, argv):
+
+ # Check first argument for type
+ argv = self.getTypeArgument(argv)
+ if argv is None:
+ return 0
+
+ opts, args = getopt.getopt(argv, 'f:h', ["help", "uid=",])
+
+ for name, value in opts:
+ if name == "-f":
+ self.path = value
+ elif name in ("-h", "--help"):
+ self.usage()
+ return 1
+ elif name == "--uid":
+ self.uid = value
+ else:
+ print "Unknown option: %s." % (name,)
+ self.usage()
+ return 0
+
+ if not self.path:
+ print "Must specify a path."
+ self.usage()
+ return 0
+ if not self.uid:
+ print "Must specify a UID."
+ self.usage()
+ return 0
+ if args:
+ print "Arguments not allowed."
+ self.usage()
+ return 0
+
+ if not self.loadAccounts():
+ return 0
+ return self.doCommand()
+
+ def doCommand(self):
+ if self.doChangePassword():
+ return self.writeAccounts()
+ return 0
+
+ def doChangePassword(self):
+
+ # First check record exists
+ record = self.directory.getRecord(self.recordType, self.uid)
+ if record is None:
+ print "No '%s' record matching uid '%s'" % (self.recordType, self.uid,)
+ return 0
+
+ record.password = self.promptPassword()
+ return 1
+
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/command.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/command.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/command.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,187 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from admin.xmlaccounts.directory import XMLDirectory
+from xml.etree.ElementTree import XML
+from StringIO import StringIO
+from protocol.utils.xmlhelpers import BetterElementTree
+from admin.xmlaccounts import recordtypes
+from getpass import getpass
+
+import getopt
+
+class Command(object):
+
+ def __init__(self, cmdname, description):
+ self.path = None
+ self.cmdname = cmdname
+ self.description = description
+ self.recordType = None
+
+ def usage(self):
+ if self.allRecordsAllowed():
+ print """USAGE: %s [TYPE] [OPTIONS]
+
+TYPE: One of "all", "users", "groups", "locations" or "resources". Also,
+"a", "u", "g", "l" or "r" as shortcuts. Invalid or missing type is
+treated as "all".
+
+Options:
+ -f file path to accounts.xml
+""" % (self.cmdname,)
+ else:
+ print """USAGE: %s TYPE [OPTIONS]
+
+TYPE: One of "users", "groups", "locations" or "resources". Also,
+"u", "g", "l" or "r" as shortcuts.
+
+Options:
+ -f file path to accounts.xml
+""" % (self.cmdname,)
+
+ def allRecordsAllowed(self):
+ return False
+
+ def execute(self, argv):
+
+ # Check first argument for type
+ argv = self.getTypeArgument(argv)
+ if argv is None:
+ return 0
+
+ opts, args = getopt.getopt(argv, 'f:h', ["help", ])
+
+ for name, value in opts:
+ if name == "-f":
+ self.path = value
+ elif name in ("-h", "--help"):
+ self.usage()
+ return 1
+ else:
+ print "Unknown option: %s." % (name,)
+ self.usage()
+ return 0
+
+ if not self.path:
+ print "Must specify a path."
+ self.usage()
+ return 0
+ if args:
+ print "Arguments not allowed."
+ self.usage()
+ return 0
+
+ if not self.loadAccounts():
+ return 0
+ return self.doCommand()
+
+ def getTypeArgument(self, argv):
+ # Check first argument for type
+ if len(argv) == 0:
+ print "Must specify a record type."
+ self.usage()
+ return None
+ type = argv[0]
+ type = self.mapType(type)
+ if not type and not self.allRecordsAllowed():
+ print "Invalid type '%s'." % (argv[0],)
+ self.usage()
+ return None
+ self.recordType = type if type else recordtypes.recordType_all
+ if type:
+ return argv[1:]
+ else:
+ return argv
+
+ def mapType(self, type):
+ return {
+ "users" : recordtypes.recordType_users,
+ "u" : recordtypes.recordType_users,
+ "groups" : recordtypes.recordType_groups,
+ "g" : recordtypes.recordType_groups,
+ "locations": recordtypes.recordType_locations,
+ "l" : recordtypes.recordType_locations,
+ "resources": recordtypes.recordType_resources,
+ "r" : recordtypes.recordType_resources,
+ "all" : recordtypes.recordType_all,
+ "a" : recordtypes.recordType_all,
+ }.get(type, None)
+
+ def loadAccounts(self):
+
+ f = open(self.path, "r")
+ if not f:
+ print "Could not open file: %s" % (self.path,)
+ return 0
+ xmldata = f.read()
+ f.close()
+ self.directory = XMLDirectory()
+ self.directory.parseXML(XML(xmldata))
+ return 1
+
+ def writeAccounts(self):
+
+ node = self.directory.writeXML()
+ os = StringIO()
+ xmldoc = BetterElementTree(node)
+ xmldoc.writeUTF8(os)
+ f = open(self.path, "w")
+ if not f:
+ print "Could not open file: %s for writing" % (self.path,)
+ return 0
+ f.write(os.getvalue())
+ f.close()
+ return 1
+
+ def doCommand(self):
+ pass
+
+ def promptPassword(self):
+ """
+ Prompt the user for a password.
+ """
+ while True:
+ password = getpass("Password: ")
+ temp = getpass("Password (again): ")
+ if temp != password:
+ print "Passwords do not match. Try again."
+ else:
+ return password
+
+ def getMemberList(self, prompt, title, type):
+ """
+ Prompt the user for a list of members.
+ """
+ results = []
+ print prompt
+ while True:
+ memberType = raw_input("%s type [u/g/l/r or leave empty to stop adding %s]: " % (title, type,))
+ if memberType in ("u", "g", "l", "r",):
+ memberUid = raw_input("%s uid [leave empty to stop adding %s]: " % (title, type,))
+ if memberUid:
+ # Verify that member type exists
+ recordType = self.mapType(memberType)
+ if self.directory.containsRecord(recordType, memberUid):
+ results.append((recordType, memberUid,))
+ else:
+ print "Record uid: '%s 'of type: '%s' does not exist in the directory." % (memberUid, recordType,)
+ else:
+ break
+ elif memberType:
+ print "Member type must be one of 'u' (users), 'g' (groups), 'l' (locations) or 'r' (resources)."
+ else:
+ break
+ return results
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/listrecords.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/listrecords.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/listrecords.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,125 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from admin.xmlaccounts.commands.command import Command
+from admin.xmlaccounts import recordtypes
+import itertools
+
+class ListRecords(Command):
+
+ CMDNAME = "list"
+
+ def __init__(self):
+ super(ListRecords, self).__init__(self.CMDNAME, "List all records of the specified type.")
+
+ def allRecordsAllowed(self):
+ return True
+
+ def doCommand(self):
+ self.listRecords(self.recordType)
+
+ def listRecords(self, recordType):
+
+ if recordType == recordtypes.recordType_all:
+ users = [l for l in self.directory.records.itervalues()]
+ print "Full List\n"
+ else:
+ users = (self.directory.records[recordType],)
+ print "%s List\n" % (recordType.capitalize(),)
+
+
+ table = [
+ ["UID", "GUID", "Name", "CUADDR",]
+ ]
+ if recordType == recordtypes.recordType_all:
+ table[0].insert(0, "TYPE")
+ table[0].append("MEMBERS")
+ table[0].append("PROXIES")
+ elif recordType in (recordtypes.recordType_groups,):
+ table[0].append("MEMBERS")
+ elif recordType in (recordtypes.recordType_locations, recordtypes.recordType_resources,):
+ table[0].append("PROXIES")
+ for user in itertools.chain(*users):
+ if len(table) > 1:
+ table.append(None)
+ cuaddrs = user.calendarUserAddresses if user.calendarUserAddresses else ("",)
+ if user.recordType in (recordtypes.recordType_groups,):
+ members = user.members if user.members else (None,)
+ elif user.recordType in (recordtypes.recordType_locations, recordtypes.recordType_resources,):
+ members = user.proxies if user.proxies else (None,)
+ else:
+ members = (None,)
+ for ctr, items in enumerate(map(None, cuaddrs, members,)):
+ cuaddr, member = items
+ if cuaddr is None:
+ cuaddr = ""
+ if member is None:
+ member = ""
+ else:
+ member = "(%s) %s" % (member[0], member[1],)
+ if recordType == recordtypes.recordType_all:
+ row = (user.recordType,)
+ else:
+ row = ()
+ row += (
+ user.uid if not ctr else "",
+ user.guid if not ctr else "",
+ user.name if not ctr else "",
+ cuaddr,
+ )
+ if recordType == recordtypes.recordType_all:
+ if user.recordType in (recordtypes.recordType_groups,):
+ row += (member, "")
+ elif user.recordType in (recordtypes.recordType_locations, recordtypes.recordType_resources,):
+ row += ("", member,)
+ else:
+ row += ("", "")
+ elif user.recordType in (recordtypes.recordType_groups, recordtypes.recordType_locations, recordtypes.recordType_resources,):
+ row += (member,)
+ table.append(row)
+
+ self.printTable(table)
+ return 1
+
+ def printTable(self, table):
+
+ maxWidths = [0 for _ignore in table[0]]
+ for row in table:
+ if row is not None:
+ for ctr, col in enumerate(row):
+ maxWidths[ctr] = max(maxWidths[ctr], len(col) if col else 0)
+
+ self.printDivider(maxWidths, False)
+ for rowctr, row in enumerate(table):
+ if row is None:
+ self.printDivider(maxWidths)
+ else:
+ print "|",
+ for colctr, col in enumerate(row):
+ print "%- *s" % (maxWidths[colctr], col if col else ""),
+ print "|",
+ print ""
+ if not rowctr:
+ self.printDivider(maxWidths)
+ self.printDivider(maxWidths, False)
+
+ def printDivider(self, maxWidths, intermediate=True):
+ t = "|" if intermediate else "+"
+ for widthctr, width in enumerate(maxWidths):
+ t += "-"
+ t += "-" * width
+ t += "-+" if widthctr < len(maxWidths) - 1 else ("-|" if intermediate else "-+")
+ print t
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/removerecord.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/removerecord.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/commands/removerecord.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,97 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from admin.xmlaccounts.commands.command import Command
+import getopt
+
+class RemoveRecord(Command):
+
+ CMDNAME = "remove"
+
+ def __init__(self):
+ super(RemoveRecord, self).__init__(self.CMDNAME, "Remove a record of the specified type.")
+ self.uid = None
+
+ def usage(self):
+ print """USAGE: %s TYPE [OPTIONS]
+
+TYPE: One of "users", "groups", "locations" or "resources". Also,
+"u", "g", "l" or "r" as shortcuts.
+
+Options:
+ -f file path to accounts.xml
+ --uid UID to remove
+""" % (self.cmdname,)
+
+ def execute(self, argv):
+
+ # Check first argument for type
+ argv = self.getTypeArgument(argv)
+ if argv is None:
+ return 0
+
+ opts, args = getopt.getopt(argv, 'f:h', ["help", "uid=",])
+
+ for name, value in opts:
+ if name == "-f":
+ self.path = value
+ elif name in ("-h", "--help"):
+ self.usage()
+ return 1
+ elif name == "--uid":
+ self.uid = value
+ else:
+ print "Unknown option: %s." % (name,)
+ self.usage()
+ return 0
+
+ if not self.path:
+ print "Must specify a path."
+ self.usage()
+ return 0
+ if not self.uid:
+ print "Must specify a UID."
+ self.usage()
+ return 0
+ if args:
+ print "Arguments not allowed."
+ self.usage()
+ return 0
+
+ if not self.loadAccounts():
+ return 0
+ return self.doCommand()
+
+ def doCommand(self):
+ if self.doRemove():
+ return self.writeAccounts()
+ return 0
+
+ def doRemove(self):
+
+ # First check record exists
+ record = self.directory.getRecord(self.recordType, self.uid)
+ if record is None:
+ print "No '%s' record matching uid '%s'" % (self.recordType, self.uid,)
+ return 0
+
+
+ confirm = raw_input("Really delete the record for '%s' in '%s' [y/n]?" % (self.uid, self.recordType,))
+ if confirm != "y":
+ return 0
+
+ self.directory.removeRecord(self.recordType, self.uid)
+ return 1
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/directory.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/directory.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/directory.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,86 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from admin.xmlaccounts import recordtypes
+from admin.xmlaccounts import tags
+from admin.xmlaccounts.record import XMLRecord
+
+from xml.etree.ElementTree import Element
+
+class XMLDirectory(object):
+
+ def __init__(self):
+ self.realm = ""
+ self.records = {}
+ for type in recordtypes.RECORD_TYPES:
+ self.records[type] = []
+
+ def addRecord(self, record):
+ self.records[record.recordType].append(record)
+
+ def containsRecord(self, recordType, uid):
+ for record in self.records[recordType]:
+ if record.uid == uid:
+ return True
+ else:
+ return False
+
+ def containsGUID(self, guid):
+ for type in recordtypes.RECORD_TYPES:
+ for record in self.records[type]:
+ if record.guid == guid:
+ return True
+ return False
+
+ def getRecord(self, recordType, uid):
+ for record in self.records[recordType]:
+ if record.uid == uid:
+ return record
+ else:
+ return None
+
+ def removeRecord(self, recordType, uid):
+ for record in self.records[recordType]:
+ if record.uid == uid:
+ self.records[recordType].remove(record)
+ return True
+ else:
+ return False
+
+ def parseXML(self, node):
+
+ if node.tag == tags.ELEMENT_ACCOUNTS:
+ self.realm = node.get(tags.ATTRIBUTE_REALM, "")
+
+ for child in node.getchildren():
+ record = XMLRecord()
+ record.parseXML(child)
+ self.records[record.recordType].append(record)
+
+ # Now resolve group and proxy references
+
+
+ def writeXML(self):
+ root = Element(tags.ELEMENT_ACCOUNTS)
+ if self.realm:
+ root.set(tags.ATTRIBUTE_REALM, self.realm)
+ for type in recordtypes.RECORD_TYPES:
+ self.writeXMLRecords(root, self.records[type])
+ return root
+
+ def writeXMLRecords(self, root, records):
+ for record in records:
+ root.append(record.writeXML())
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/manage.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/manage.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/manage.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,48 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+#import admin.xmlaccounts.commands
+from admin.xmlaccounts.commands import registered
+
+import sys
+
+def usage():
+ cmds = registered.keys()
+ cmds.sort()
+ print """USAGE: manage CMD [OPTIONS]
+
+CMD: one of:
+%s
+
+OPTIONS: specific to each command, use --help with the
+command to see what options are supported.
+""" % ("\n".join(["\t%s" % (cmd,) for cmd in cmds]),)
+
+def runit():
+ # Dispatch a command based on the first argument
+ if len(sys.argv) == 1:
+ usage()
+ sys.exit(0)
+
+ if registered.has_key(sys.argv[1]):
+ sys.exit(registered[sys.argv[1]]().execute(sys.argv[2:]))
+ else:
+ print "No command called '%s' is available." % (sys.argv[1],)
+ usage()
+ sys.exit(0)
+
+if __name__ == '__main__':
+ runit()
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/record.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/record.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/record.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,106 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from admin.xmlaccounts import recordtypes
+from admin.xmlaccounts import tags
+
+from protocol.utils.xmlhelpers import SubElementWithData
+
+from xml.etree.ElementTree import Element
+
+class XMLRecord(object):
+
+ def __init__(self):
+ self.recordType = None
+ self.repeat = 0
+ self.uid = None
+ self.guid = None
+ self.password = None
+ self.name = None
+ self.members = set()
+ self.calendarUserAddresses = set()
+ self.autoSchedule = False
+ self.enabledForCalendaring = True
+ self.proxies = set()
+ self.proxyFor = set()
+
+ def parseXML(self, node):
+ self.recordType = recordtypes.TAGS_TO_RECORD_TYPES[node.tag]
+ self.repeat = int(node.get(tags.ATTRIBUTE_REPEAT, "0"))
+ for child in node.getchildren():
+ if child.tag == tags.ELEMENT_UID:
+ self.uid = child.text
+ elif child.tag == tags.ELEMENT_GUID:
+ self.guid = child.text
+ elif child.tag == tags.ELEMENT_PASSWORD:
+ self.password = child.text
+ elif child.tag == tags.ELEMENT_NAME:
+ self.name = child.text
+ elif child.tag == tags.ELEMENT_MEMBERS:
+ self._parseMembers(child, self.members)
+ elif child.tag == tags.ELEMENT_CUADDR:
+ self.calendarUserAddresses.add(child.text)
+ elif child.tag == tags.ELEMENT_AUTOSCHEDULE:
+ # Only Resources & Locations
+ if self.recordType not in (recordtypes.recordType_resources, recordtypes.recordType_locations,):
+ raise ValueError("<auto-schedule> element only allowed for Resources and Locations: %s" % (child.tag,))
+ self.autoSchedule = True
+ elif child.tag == tags.ELEMENT_DISABLECALENDAR:
+ # Only Groups
+ if self.recordType not in (recordtypes.recordType_users, recordtypes.recordType_groups,):
+ raise ValueError("<disable-calendar> element only allowed for Groups: %s" % (child.tag,))
+ self.enabledForCalendaring = False
+ elif child.tag == tags.ELEMENT_PROXIES:
+ # Only Resources & Locations
+ if self.recordType not in (recordtypes.recordType_resources, recordtypes.recordType_locations,):
+ raise ValueError("<proxies> element only allowed for Resources and Locations: %s" % (child.tag,))
+ self._parseMembers(child, self.proxies)
+ else:
+ raise RuntimeError("Unknown account attribute: %s" % (child.tag,))
+
+ def _parseMembers(self, node, addto):
+ for child in node.getchildren():
+ if child.tag == tags.ELEMENT_MEMBER:
+ recordType = child.get(tags.ATTRIBUTE_RECORDTYPE, recordtypes.recordType_users)
+ addto.add((recordType, child.text))
+
+ def writeXML(self):
+
+ root = Element(recordtypes.RECORD_TYPES_TO_TAGS[self.recordType])
+ if self.repeat:
+ root.set(tags.ATTRIBUTE_REPEAT, str(self.repeat))
+
+ SubElementWithData(root, tags.ELEMENT_UID, self.uid)
+ SubElementWithData(root, tags.ELEMENT_GUID, self.guid)
+ SubElementWithData(root, tags.ELEMENT_PASSWORD, self.password)
+ SubElementWithData(root, tags.ELEMENT_NAME, self.name)
+ if self.recordType == recordtypes.recordType_groups:
+ members = SubElementWithData(root, tags.ELEMENT_MEMBERS)
+ for member in self.members:
+ SubElementWithData(members, tags.ELEMENT_MEMBER, member[1], {tags.ATTRIBUTE_RECORDTYPE:member[0]})
+ if self.calendarUserAddresses:
+ for cuaddr in self.calendarUserAddresses:
+ SubElementWithData(root, tags.ELEMENT_CUADDR, cuaddr)
+ if self.recordType in (recordtypes.recordType_resources, recordtypes.recordType_locations,) and self.autoSchedule:
+ SubElementWithData(root, tags.ELEMENT_AUTOSCHEDULE)
+ if self.recordType in (recordtypes.recordType_users, recordtypes.recordType_groups,) and not self.enabledForCalendaring:
+ SubElementWithData(root, tags.ELEMENT_DISABLECALENDAR)
+ if self.recordType in (recordtypes.recordType_resources, recordtypes.recordType_locations,):
+ proxies = SubElementWithData(root, tags.ELEMENT_PROXIES)
+ for proxy in self.proxies:
+ SubElementWithData(proxies, tags.ELEMENT_MEMBER, proxy[1], {tags.ATTRIBUTE_RECORDTYPE:proxy[0]})
+
+ return root
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/recordtypes.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/recordtypes.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/recordtypes.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,44 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from admin.xmlaccounts import tags
+
+recordType_users = "users"
+recordType_groups = "groups"
+recordType_locations = "locations"
+recordType_resources = "resources"
+recordType_all = "all"
+
+RECORD_TYPES = (
+ recordType_users,
+ recordType_groups,
+ recordType_locations,
+ recordType_resources,
+)
+
+RECORD_TYPES_TO_TAGS = {
+ recordType_users : tags.ELEMENT_USER,
+ recordType_groups : tags.ELEMENT_GROUP,
+ recordType_locations : tags.ELEMENT_LOCATION,
+ recordType_resources : tags.ELEMENT_RESOURCE,
+}
+
+TAGS_TO_RECORD_TYPES = {
+ tags.ELEMENT_USER : recordType_users,
+ tags.ELEMENT_GROUP : recordType_groups,
+ tags.ELEMENT_LOCATION : recordType_locations,
+ tags.ELEMENT_RESOURCE : recordType_resources,
+}
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tags.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tags.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tags.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,36 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+ELEMENT_ACCOUNTS = "accounts"
+ELEMENT_USER = "user"
+ELEMENT_GROUP = "group"
+ELEMENT_LOCATION = "location"
+ELEMENT_RESOURCE = "resource"
+
+ELEMENT_UID = "uid"
+ELEMENT_GUID = "guid"
+ELEMENT_PASSWORD = "password"
+ELEMENT_NAME = "name"
+ELEMENT_MEMBERS = "members"
+ELEMENT_MEMBER = "member"
+ELEMENT_CUADDR = "cuaddr"
+ELEMENT_AUTOSCHEDULE = "auto-schedule"
+ELEMENT_DISABLECALENDAR = "disable-calendar"
+ELEMENT_PROXIES = "proxies"
+
+ATTRIBUTE_REALM = "realm"
+ATTRIBUTE_REPEAT = "repeat"
+ATTRIBUTE_RECORDTYPE = "type"
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,16 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/test_directory.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/test_directory.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/test_directory.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,80 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from StringIO import StringIO
+from admin.xmlaccounts.directory import XMLDirectory
+from protocol.utils.xmlhelpers import BetterElementTree
+from xml.etree.ElementTree import XML
+
+import unittest
+
+class TestDirectory(unittest.TestCase):
+
+ def checkXML(self, x):
+
+ x = x.replace("\n", "\r\n")
+
+ # Parse the XML data
+ a = XMLDirectory()
+ a.parseXML(XML(x))
+
+ # Generate the XML data
+ node = a.writeXML()
+ os = StringIO()
+ xmldoc = BetterElementTree(node)
+ xmldoc.writeUTF8(os)
+
+ # Verify data
+ self.assertEqual(os.getvalue(), x)
+
+ def test_accounts(self):
+
+ self.checkXML("""<?xml version='1.0' encoding='utf-8'?>
+<accounts realm="Test Realm">
+ <user>
+ <uid>admin</uid>
+ <guid>12345</guid>
+ <password>admin</password>
+ <name>Super User</name>
+ </user>
+ <user>
+ <uid>test</uid>
+ <guid />
+ <password>test</password>
+ <name>Test User</name>
+ <cuaddr>mailto:testuser at example.com</cuaddr>
+ </user>
+ <group>
+ <uid>users</uid>
+ <guid>123456</guid>
+ <password>users</password>
+ <name>Users Group</name>
+ <members>
+ <member type="users">test</member>
+ </members>
+ </group>
+ <location>
+ <uid>mercury</uid>
+ <guid>1234567</guid>
+ <password>mercury</password>
+ <name>Mecury Conference Room, Building 1, 2nd Floor</name>
+ <auto-schedule />
+ <proxies>
+ <member type="users">test</member>
+ </proxies>
+ </location>
+</accounts>
+""")
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/test_record.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/test_record.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/test_record.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,82 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from StringIO import StringIO
+from admin.xmlaccounts.record import XMLRecord
+from protocol.utils.xmlhelpers import BetterElementTree
+from xml.etree.ElementTree import XML
+
+import unittest
+
+class TestRecord(unittest.TestCase):
+
+ def checkXML(self, x):
+
+ x = x.replace("\n", "\r\n")
+
+ # Parse the XML data
+ a = XMLRecord()
+ a.parseXML(XML(x))
+
+ # Generate the XML data
+ node = a.writeXML()
+ os = StringIO()
+ xmldoc = BetterElementTree(node)
+ xmldoc.writeUTF8(os)
+
+ # Verify data
+ self.assertEqual(os.getvalue(), x)
+
+ def test_user(self):
+
+ self.checkXML("""<?xml version='1.0' encoding='utf-8'?>
+<user>
+ <uid>test</uid>
+ <guid />
+ <password>test</password>
+ <name>Test User</name>
+ <cuaddr>mailto:testuser at example.com</cuaddr>
+</user>
+""")
+
+ def test_group(self):
+
+ self.checkXML("""<?xml version='1.0' encoding='utf-8'?>
+<group>
+ <uid>users</uid>
+ <guid>12345</guid>
+ <password>users</password>
+ <name>Users Group</name>
+ <members>
+ <member type="users">test</member>
+ </members>
+</group>
+""")
+
+ def test_location(self):
+
+ self.checkXML("""<?xml version='1.0' encoding='utf-8'?>
+<location>
+ <uid>mercury</uid>
+ <guid>12345</guid>
+ <password>mercury</password>
+ <name>Mecury Conference Room, Building 1, 2nd Floor</name>
+ <auto-schedule />
+ <proxies>
+ <member type="users">test</member>
+ </proxies>
+</location>
+""")
Added: CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/utils.py
===================================================================
--- CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/utils.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/admin/xmlaccounts/tests/utils.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,41 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from StringIO import StringIO
+from admin.xmlaccounts.record import XMLRecord
+from protocol.utils.xmlhelpers import BetterElementTree
+from xml.etree.ElementTree import XML
+
+import unittest
+
+class TestCommon(unittest.TestCase):
+
+ def checkXML(self, x):
+
+ x = x.replace("\n", "\r\n")
+
+ # Parse the XML data
+ a = XMLRecord()
+ a.parseXML(XML(x))
+
+ # Generate the XML data
+ node = a.writeXML()
+ os = StringIO()
+ xmldoc = BetterElementTree(node)
+ xmldoc.writeUTF8(os)
+
+ # Verify data
+ self.assertEqual(os.getvalue(), x)
Added: CalDAVClientLibrary/trunk/src/browser/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2007-2008 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.
+##
Added: CalDAVClientLibrary/trunk/src/browser/baseshell.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/baseshell.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/baseshell.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,208 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser import utils
+from browser.command import CommandError
+from browser.command import UnknownCommand
+from protocol.url import URL
+from protocol.webdav.definitions import davxml
+import os
+import readline
+import traceback
+
+class BaseShell(object):
+
+ def __init__(self, history_name):
+
+ self.prefix = ""
+ self.commands = {}
+ self.history = []
+ self.history_name = history_name
+ self.preserve_history = False
+ self.last_wd_complete = ("", ())
+
+ self.readHistory()
+
+ def readHistory(self):
+ try:
+ readline.read_history_file(os.path.expanduser("~/.%s" % (self.history_name,)))
+ except IOError:
+ pass
+
+ def saveHistory(self):
+ readline.write_history_file(os.path.expanduser("~/.%s" % (self.history_name,)))
+
+ def registerCommands(self, cmds):
+ raise NotImplementedError
+
+ def registerCommand(self, command):
+ for cmd in command.getCmds():
+ self.commands[cmd] = command
+ command.setShell(self)
+
+ def run(self):
+
+ # Preserve existing history
+ if self.preserve_history:
+ old_history = [readline.get_history_item(index) for index in xrange(readline.get_current_history_length())]
+ readline.clear_history()
+ map(readline.add_history, self.history)
+
+ readline.set_completer(self.complete)
+ readline.parse_and_bind("bind ^I rl_complete")
+
+ while True:
+ cmdline = raw_input("%s > " % (self.prefix,))
+ self.last_wd_complete = ("", ())
+ if not cmdline:
+ continue
+
+ # Try to dispatch command
+ try:
+ self.execute(cmdline)
+ except SystemExit, e:
+ print "Exiting shell: %s" % (e.message,)
+ break
+ except UnknownCommand, e:
+ print "Command '%s' unknown." % (e.message,)
+ except Exception, e:
+ traceback.print_exc()
+
+ # Restore previous history
+ if self.preserve_history:
+ self.saveHistory()
+ readline.clear_history()
+ map(readline.add_history, old_history)
+
+ def execute(self, cmdline):
+
+ # Check for history recall
+ if cmdline == "!!" and self.history:
+ cmdline = self.history[-1]
+ print cmdline
+ if readline.get_current_history_length():
+ readline.replace_history_item(readline.get_current_history_length() - 1, cmdline)
+ elif cmdline.startswith("!"):
+ try:
+ index = int(cmdline[1:])
+ if index> 0 and index <= len(self.history):
+ cmdline = self.history[index-1]
+ print cmdline
+ if readline.get_current_history_length():
+ readline.replace_history_item(readline.get_current_history_length() - 1, cmdline)
+ else:
+ raise ValueError()
+ except ValueError:
+ print "%s: event not found" % (cmdline,)
+ return
+
+ # split the command line into command and options
+ splits = cmdline.split(" ", 1)
+ cmd = splits[0]
+ options = splits[1] if len(splits) == 2 else ""
+
+ # Find matching command
+ try:
+ if cmd not in self.commands:
+ self.history.append(cmdline)
+ raise UnknownCommand(cmd)
+ else:
+ self.commands[cmd].execute(cmd, options)
+ finally:
+ # Store in history
+ self.history.append(cmdline)
+
+ def help(self, cmd=None):
+
+ if cmd:
+ if cmd in self.commands:
+ cmds = ((cmd, self.commands[cmd]),)
+ full_help = True
+ else:
+ raise CommandError("Command could not be found: %s" % (cmd,))
+ else:
+ cmds = self.commands.keys()
+ cmds.sort()
+ cmds = [(cmd, self.commands[cmd]) for cmd in cmds]
+ full_help = False
+
+ if full_help:
+ if self.commands[cmd].hasHelp(cmd):
+ print self.commands[cmd].help(cmd)
+ else:
+ results = []
+ for name, cmd in cmds:
+ if cmd.hasHelp(name):
+ results.append(cmd.helpListing(name))
+ utils.printTwoColumnList(results)
+
+ def complete(self, text, state):
+
+ # If there is no space in the text we complete a command
+ #print "complete: %s %d" % (text, state)
+ results = []
+ check = readline.get_line_buffer()[:readline.get_endidx()].lstrip()
+ checklen = len(check)
+ if " " not in check:
+ for cmd in self.commands:
+ if cmd[:checklen] == check:
+ results.append(cmd)
+ else:
+ cmd, rest = check.split(" ", 1)
+ if cmd in self.commands:
+ results = self.commands[cmd].complete(rest)
+
+ return results[state]
+
+ def wdcomplete(self, text):
+
+ #print "\nwdcomplete: %s" % (text,)
+
+ # Look at cache and return that
+ if self.last_wd_complete[0] == text:
+ return self.last_wd_complete[1]
+
+ # Look for relative vs absolute
+ if text[0] == "/":
+ dirname, _ignore_child = os.path.split(text)
+ path = dirname
+ if not path.endswith("/"):
+ path += "/"
+ pathlen = 0
+ else:
+ path = self.wd
+ pathlen = len(path) + (0 if path.endswith("/") else 1)
+ dirname, _ignore_child = os.path.split(text)
+ if dirname:
+ path = os.path.join(path, dirname)
+ if not path.endswith("/"):
+ path += "/"
+
+ #print "pdc: %s, %s, %s, %s" % (self.wd, path, dirname, child)
+ resource = URL(url=path)
+
+ props = (davxml.resourcetype,)
+ results = self.account.session.getPropertiesOnHierarchy(resource, props)
+ #print results.keys()
+ results = [result[pathlen:] for result in results.iterkeys() if len(result) > pathlen]
+ #print results
+ if text:
+ textlen = len(text)
+ results = [result for result in results if result[:textlen] == text]
+ #print results
+
+ self.last_wd_complete = (text, results,)
+ return results
Added: CalDAVClientLibrary/trunk/src/browser/command.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/command.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/command.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,62 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+class Command(object):
+
+ def __init__(self):
+
+ self.shell = None
+ self.cmds = ()
+
+ def execute(self, name, options):
+ raise NotImplementedError
+
+ def usage(self, name):
+ raise NotImplementedError
+
+ def hasHelp(self, name):
+ return name in self.cmds
+
+ def help(self, name):
+ result = "Command: %s\n" % (name,)
+ result += "Description: %s\n" % (self.helpDescription(),)
+ result += self.usage(name)
+ return result
+
+ def helpListing(self, name):
+ return (name, self.helpDescription())
+
+ def helpDescription(self):
+ return ""
+
+ def setShell(self, shell):
+ self.shell = shell
+
+ def getCmds(self):
+ return self.cmds
+
+ def complete(self, text):
+ return ()
+
+class WrongOptions(Exception):
+ pass
+
+class UnknownCommand(Exception):
+ pass
+
+class CommandError(Exception):
+ pass
+
Added: CalDAVClientLibrary/trunk/src/browser/commands/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/commands/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/commands/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,31 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+__all__ = [
+ "acl",
+ "cat",
+ "cd",
+ "help",
+ "history",
+ "logging",
+ "ls",
+ "principal",
+ "props",
+ "proxies",
+ "quit",
+ "server",
+ "whoami",
+]
Added: CalDAVClientLibrary/trunk/src/browser/commands/acl.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/commands/acl.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/commands/acl.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,372 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser.command import Command
+from browser.command import WrongOptions
+from protocol.url import URL
+from protocol.webdav.definitions import davxml
+from browser.subshell import SubShell
+from browser import commands
+from protocol.webdav.ace import ACE
+from browser import utils
+from protocol.caldav.definitions import caldavxml
+from xml.etree.ElementTree import QName
+import readline
+import os
+import getopt
+
+class Cmd(Command):
+
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("acl",)
+ self.subshell = None
+
+ def execute(self, name, options):
+
+ interactive = False
+ path = None
+
+ try:
+ opts, args = getopt.getopt(options.split(), 'i')
+ except getopt.GetoptError, e:
+ print str(e)
+ print self.usage(name)
+ raise WrongOptions
+
+ for name, _ignore_value in opts:
+
+ if name == "-i":
+ interactive = True
+ else:
+ print "Unknown option: %s" % (name,)
+ print self.usage(name)
+ raise WrongOptions
+
+ if len(args) > 1:
+ print "Wrong number of arguments: %d" % (len(args),)
+ print self.usage(name)
+ raise WrongOptions
+ elif args:
+ path = args[0]
+ if not path.startswith("/"):
+ path = os.path.join(self.shell.wd, path)
+ else:
+ path = self.shell.wd
+ if not path.endswith("/"):
+ path += "/"
+ resource = URL(url=path)
+
+ results, bad = self.shell.account.session.getProperties(resource, (davxml.acl,))
+ if davxml.acl in bad:
+ print "Could not retrieve DAV:acl property, status=%d" % (bad[davxml.acl],)
+ else:
+ if interactive:
+ self.doInteractiveMode(resource, results[davxml.acl])
+ else:
+ aces = ACE.parseFromACL(results[davxml.acl])
+ print utils.printACEList(aces, self.shell.account)
+
+ return True
+
+ def doInteractiveMode(self, resource, acls):
+
+ print "Entering ACL edit mode on resource: %s" % (resource.relativeURL(),)
+ if not self.subshell:
+ self.subshell = SubShell(self.shell, "ACL", (
+ commands.help.Cmd(),
+ commands.logging.Cmd(),
+ commands.quit.Cmd(),
+ Add(),
+ Change(),
+ Remove(),
+ List(),
+ ))
+ self.subshell.resource = resource
+ self.subshell.account = self.shell.account
+ self.subshell.run()
+
+ def usage(self, name):
+ return """Usage: %s [OPTIONS] [PATH]
+PATH is a relative or absolute path.
+
+Options:
+-i interactive mode for adding, changing and deleting ACLs.
+ if not present, existing ACLs will be printed.
+""" % (name,)
+
+ def helpDescription(self):
+ return "Manage the access privileges of a directory or file."
+
+class CommonACLCommand(Command):
+
+ def displayACEList(self):
+ # First list the current set
+ results, bad = self.shell.shell.account.session.getProperties(self.shell.resource, (davxml.acl,))
+ if davxml.acl in bad:
+ print "Could not retrieve DAV:acl property, status=%d" % (bad[davxml.acl],)
+ return None
+ else:
+ aces = ACE.parseFromACL(results[davxml.acl])
+ print utils.printACEList(aces, self.shell.shell.account)
+ return aces
+
+ def createACE(self, oldace=None):
+
+ ace = ACE()
+ print "Principal Type:"
+ print " 1. Principal path"
+ print " 2. All"
+ print " 3. Authenticated"
+ print " 4. Unauthenticated"
+ print " 5. Property"
+ insert = None
+ if oldace:
+ mapper = {
+ str(davxml.href): "1",
+ str(davxml.all): "2",
+ str(davxml.authenticated): "3",
+ str(davxml.unauthenticated): "4",
+ str(davxml.property): "5",
+ }
+ insert = mapper.get(oldace.principal)
+ choice = utils.numericInput("Select type: ", 1, 5, insert=insert)
+ if choice == "q":
+ return None
+
+ if choice == 1:
+ href = utils.textInput("Enter principal path: ", insert=oldace.data if oldace else None)
+ principal = self.shell.shell.account.getPrincipal(URL(url=href))
+ ace.principal = str(davxml.href)
+ ace.data = principal.principalURL.relativeURL()
+ elif choice == 2:
+ ace.principal = str(davxml.all)
+ elif choice == 3:
+ ace.principal = str(davxml.authenticated)
+ elif choice == 4:
+ ace.principal = str(davxml.unauthenticated)
+ elif choice == 5:
+ prop = utils.textInput("Enter property qname: ", insert=str(oldace.data) if oldace else None)
+ ace.principal = str(davxml.property)
+ ace.data = QName(prop)
+
+ invert = utils.yesNoInput("Invert principal [y/n]: ", insert=("y" if oldace.invert else "n") if oldace else None)
+ ace.invert = (invert == "y")
+
+ grant = utils.choiceInput("Grant or Deny privileges [g/d]: ", ("g", "d",), insert=("g" if oldace.grant else "d") if oldace else None)
+ ace.grant = (grant == "g")
+
+ print "Privileges:"
+ print " a. {DAV}read"
+ print " b. {DAV}write"
+ print " c. {DAV}write-properties"
+ print " d. {DAV}write-content"
+ print " e. {DAV}read-acl"
+ print " f. {DAV}read-current-user-privilege-set"
+ print " g. {DAV}write-acl"
+ print " h. {DAV}bind"
+ print " i. {DAV}unbind"
+ print " j. {DAV}all"
+ print " k. {CALDAV}read-free-busy"
+ print " l. {CALDAV}schedule"
+ print " q. quit without changes"
+ choice = utils.multiChoiceInput(
+ "Select multiple items: ",
+ [char for char in "abcdefghijklq"],
+ )
+ if "q" in choice:
+ return None
+
+ mappedPrivs = {
+ 'a': davxml.read,
+ 'b': davxml.write,
+ 'c': davxml.write_properties,
+ 'd': davxml.write_content,
+ 'e': davxml.read_acl,
+ 'f': davxml.read_current_user_privilege_set,
+ 'g': davxml.write_acl,
+ 'h': davxml.bind,
+ 'i': davxml.unbind,
+ 'j': davxml.all,
+ 'k': caldavxml.read_free_busy,
+ 'l': caldavxml.schedule,
+ }
+ ace.privs = ()
+ for char in choice:
+ ace.privs += (mappedPrivs[char],)
+
+ return ace
+
+class Add(CommonACLCommand):
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("add",)
+
+ def execute(self, name, options):
+
+ # First list the current set
+ aces = self.displayACEList()
+ if aces:
+ # Ask user which one to delete
+ while True:
+ result = raw_input("Add ACL before [1 - %d] or cancel [q]: " % (len(aces) + 1,))
+ if readline.get_current_history_length():
+ readline.remove_history_item(readline.get_current_history_length() - 1)
+ if not result:
+ continue
+ if result[0] == "q":
+ break
+ try:
+ number = int(result)
+ if number > len(aces):
+ number = len(aces)
+ except ValueError:
+ print "Invalid input, try again."
+ continue
+
+ # Try and get the new ace
+ ace = self.createACE()
+ if not ace:
+ break
+ aces.insert(number, ace)
+
+ # Now remove those that cannot be edited
+ aces = [ace for ace in aces if ace.canChange()]
+
+ # Now execute
+ self.shell.shell.account.session.setACL(self.shell.resource, aces)
+ break
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+ def helpDescription(self):
+ return "Add ACL to existing resource."
+
+class Change(CommonACLCommand):
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("change",)
+
+ def execute(self, name, options):
+
+ # First list the current set
+ aces = self.displayACEList()
+ if aces:
+ # Ask user which one to delete
+ while True:
+ result = raw_input("Change ACL at [1 - %d] or cancel [q]: " % (len(aces),))
+ if readline.get_current_history_length():
+ readline.remove_history_item(readline.get_current_history_length() - 1)
+ if not result:
+ continue
+ if result[0] == "q":
+ break
+ try:
+ number = int(result)
+ except ValueError:
+ print "Invalid input, try again."
+ continue
+
+ # Check that the targeted ace is editable
+ if not aces[number - 1].canChange():
+ print "You cannot change a protected or inherited ace."
+ break
+
+ # Try and get the new ace
+ ace = self.createACE(oldace=aces[number - 1])
+ if not ace:
+ break
+ aces[number - 1] = ace
+
+ # Now remove those that cannot be edited
+ aces = [ace for ace in aces if ace.canChange()]
+
+ # Now execute
+ self.shell.shell.account.session.setACL(self.shell.resource, aces)
+ break
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+ def helpDescription(self):
+ return "Change ACL on existing resource."
+
+class Remove(CommonACLCommand):
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("remove",)
+
+ def execute(self, name, options):
+
+ # First list the current set
+ aces = self.displayACEList()
+ if aces:
+ # Ask user which one to delete
+ while True:
+ result = raw_input("Remove ACL [1 - %d] or cancel [q]: " % (len(aces),))
+ if readline.get_current_history_length():
+ readline.remove_history_item(readline.get_current_history_length() - 1)
+ if not result:
+ continue
+ if result[0] == "q":
+ break
+ try:
+ number = int(result)
+ except ValueError:
+ print "Invalid input, try again."
+ continue
+
+ # Check that the targeted ace is editable
+ if not aces[number-1].canChange():
+ print "You cannot remove a protected or inherited ace."
+ break
+
+ # Remove the one we are removing
+ del aces[number-1]
+
+ # Now remove those that cannot be edited
+ aces = [ace for ace in aces if ace.canChange()]
+
+ # Now execute
+ self.shell.shell.account.session.setACL(self.shell.resource, aces)
+ break
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+ def helpDescription(self):
+ return "Remove ACL on existing resource."
+
+class List(CommonACLCommand):
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("list",)
+
+ def execute(self, name, options):
+
+ self.displayACEList()
+ return True
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+ def helpDescription(self):
+ return "List current ACLs on existing resource."
Added: CalDAVClientLibrary/trunk/src/browser/commands/cat.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/commands/cat.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/commands/cat.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,55 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser.command import Command
+from browser.command import WrongOptions
+from protocol.url import URL
+import os
+import getopt
+
+class Cmd(Command):
+
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("cat",)
+
+ def execute(self, name, options):
+ opts, args = getopt.getopt(options.split(), '')
+ if len(opts) or len(args) != 1:
+ print self.usage(name)
+ raise WrongOptions()
+
+ path = args[0]
+
+ if not path.startswith("/"):
+ path = os.path.join(self.shell.wd, path)
+ resource = URL(url=path)
+
+ data, _ignore_etag = self.shell.account.session.readData(resource)
+ print data
+
+ return True
+
+ def complete(self, text):
+ return self.shell.wdcomplete(text)
+
+ def usage(self, name):
+ return """Usage: %s PATH
+PATH is a relative or absolute path.
+""" % (name,)
+
+ def helpDescription(self):
+ return "Display contents of a file or directory."
Added: CalDAVClientLibrary/trunk/src/browser/commands/cd.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/commands/cd.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/commands/cd.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,61 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser.command import Command
+from browser.command import WrongOptions
+import os
+import getopt
+
+class Cmd(Command):
+
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("cd",)
+
+ def execute(self, name, options):
+ opts, args = getopt.getopt(options.split(), '')
+ if len(opts) or len(args) != 1:
+ print self.usage(name)
+ raise WrongOptions()
+
+ newpath = args[0]
+ oldpath = self.shell.wd
+ result = True
+
+ if newpath == "..":
+ result = self.shell.setWD(os.path.dirname(oldpath))
+ elif newpath == ".":
+ pass
+ elif newpath.startswith("/"):
+ result = self.shell.setWD(newpath)
+ else:
+ result = self.shell.setWD(os.path.normpath(os.path.join(oldpath, newpath)))
+
+ if not result:
+ print "%s: %s No such directory" % (name, options,)
+
+ return result
+
+ def complete(self, text):
+ return self.shell.wdcomplete(text)
+
+ def usage(self, name):
+ return """Usage: %s PATH
+PATH is a relative or absolute path.
+""" % (name,)
+
+ def helpDescription(self):
+ return "Change working directory."
Added: CalDAVClientLibrary/trunk/src/browser/commands/help.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/commands/help.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/commands/help.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,44 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser.command import Command
+from browser.command import WrongOptions
+import getopt
+
+class Cmd(Command):
+
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("help", "?",)
+
+ def execute(self, name, options):
+ opts, args = getopt.getopt(options.split(), '')
+ if len(opts) or len(args) > 1:
+ print self.usage(name)
+ raise WrongOptions()
+ self.shell.help(cmd = (None if len(args) == 0 else args[0]))
+ return True
+
+ def usage(self, name):
+ return """Usage: %s [CMD]
+CMD is the name of a command.
+""" % (name,)
+
+ def hasHelp(self, name):
+ return name in ("help",)
+
+ def helpDescription(self):
+ return "Displays help about a command."
Added: CalDAVClientLibrary/trunk/src/browser/commands/history.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/commands/history.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/commands/history.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,41 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser.command import Command
+from browser.command import WrongOptions
+
+class Cmd(Command):
+
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("history", )
+
+ def execute(self, name, options):
+ if options:
+ print self.usage(name)
+ raise WrongOptions()
+
+ format = "%%0%ds %%s" % (len(self.shell.history),)
+ for ctr, cmd in enumerate(self.shell.history):
+ print format % (ctr+1, cmd,)
+ return True
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+ def helpDescription(self):
+ return "Displays the history of all commands used in this session."
Added: CalDAVClientLibrary/trunk/src/browser/commands/logging.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/commands/logging.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/commands/logging.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,56 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser.command import Command
+from browser.command import WrongOptions
+import getopt
+
+class Cmd(Command):
+
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("logging", )
+
+ def execute(self, name, options):
+ opts, args = getopt.getopt(options.split(), '')
+ if len(opts) or len(args) > 1:
+ print self.usage(name)
+ raise WrongOptions()
+ if args and args[0] not in ("on", "off",):
+ print self.usage(name)
+ raise WrongOptions()
+
+ if args:
+ state = args[0]
+ else:
+ state = ("off" if self.shell.account.session.loghttp else "on")
+ if state == "on":
+ self.shell.account.session.loghttp = True
+ print "HTTP logging turned on"
+ else:
+ self.shell.account.session.loghttp = False
+ print "HTTP logging turned off"
+ return True
+
+ def usage(self, name):
+ return """Usage: %s [on|off]
+on - turn HTTP protocol logging on
+off - turn HTTP protocol logging off
+without either argument - toggle the state of logging
+""" % (name,)
+
+ def helpDescription(self):
+ return "Changes the current state of HTTP logging."
Added: CalDAVClientLibrary/trunk/src/browser/commands/ls.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/commands/ls.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/commands/ls.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,93 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser.command import Command
+from browser.command import WrongOptions
+from protocol.webdav.definitions import davxml
+from protocol.url import URL
+import os
+import getopt
+
+class Cmd(Command):
+
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("ls",)
+
+ def execute(self, name, options):
+
+ longlist = False
+ path = None
+
+ opts, args = getopt.getopt(options.split(), 'l')
+
+ for name, _ignore_value in opts:
+
+ if name == "-l":
+ longlist = True
+ else:
+ print "Unknown option: %s" % (name,)
+ print self.usage(name)
+ raise WrongOptions
+
+ if len(args) > 1:
+ print "Wrong number of arguments: %d" % (len(args),)
+ print self.usage(name)
+ raise WrongOptions
+ elif args:
+ path = args[0]
+ if not path.startswith("/"):
+ path = os.path.join(self.shell.wd, path)
+ else:
+ path = self.shell.wd
+ if not path.endswith("/"):
+ path += "/"
+ resource = URL(url=path)
+
+ props = (davxml.resourcetype,)
+ if longlist:
+ props += (davxml.getcontentlength, davxml.getlastmodified,)
+ results = self.shell.account.session.getPropertiesOnHierarchy(resource, props)
+ items = results.keys()
+ items.sort()
+ for rurl in items:
+ if rurl == path:
+ continue
+ if longlist:
+ props = results[rurl]
+ size = props.get(davxml.getcontentlength, "-")
+ if not size:
+ size = "0"
+ modtime = props.get(davxml.getlastmodified, "-")
+ print "% 8s %s %s" % (size, modtime, rurl[len(path):])
+ else:
+ print rurl[len(path):]
+
+ return True
+
+ def complete(self, text):
+ return self.shell.wdcomplete(text)
+
+ def usage(self, name):
+ return """Usage: %s [OPTIONS] [PATH]
+PATH is a relative or absolute path.
+
+Options:
+-l long listing
+""" % (name,)
+
+ def helpDescription(self):
+ return "List the contents of a directory."
Added: CalDAVClientLibrary/trunk/src/browser/commands/principal.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/commands/principal.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/commands/principal.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,100 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser.command import Command
+from browser.command import WrongOptions
+from protocol.url import URL
+from browser import utils
+import getopt
+
+class Cmd(Command):
+
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("principal", )
+
+ def execute(self, name, options):
+ refresh = False
+ resolve = True
+ principal = None
+ print_proxies = False
+
+ opts, args = getopt.getopt(options.split(), 'fnp:x')
+
+ for name, value in opts:
+
+ if name == "-f":
+ refresh = True
+ elif name == "-n":
+ resolve = False
+ elif name == "-p":
+ principal = self.shell.account.getPrincipal(URL(url=value), refresh=refresh)
+ elif name == "-x":
+ print_proxies = True
+ else:
+ print "Unknown option: %s" % (name,)
+ print self.usage(name)
+ raise WrongOptions
+
+ if len(args) > 0:
+ print "Wrong number of arguments: %d" % (len(args),)
+ print self.usage(name)
+ raise WrongOptions
+
+ if not principal:
+ principal = self.shell.account.getPrincipal(refresh=refresh)
+
+ print """
+ Principal Path : %s
+ Display Name : %s
+ Principal URL : %s
+ Alternate URLs : %s
+ Group Members : %s
+ Memberships : %s
+ Calendar Homes : %s
+ Outbox URL : %s
+ Inbox URL : %s
+ Calendar Addresses: %s
+""" % (
+ principal.principalPath,
+ principal.getSmartDisplayName(),
+ principal.principalURL,
+ utils.printList(principal.alternateURIs),
+ utils.printPrincipalPaths(self.shell.account, principal.memberset, resolve, refresh),
+ utils.printPrincipalPaths(self.shell.account, principal.memberships, resolve, refresh),
+ utils.printList(principal.homeset),
+ principal.outboxURL,
+ principal.inboxURL,
+ utils.printList(principal.cuaddrs),
+ ),
+
+ if print_proxies:
+ utils.printProxyPrincipals(self.shell.account, principal, True, True, resolve, False, refresh)
+
+ return True
+
+ def usage(self, name):
+ return """Usage: %s [OPTIONS]
+Options:
+ -f force reload of all cached principals to be returned
+ -p principal path to request proxies for [OPTIONAL]
+ if not present, the current user's principal is used.
+ -n do not resolve references to other principals.
+ -x print proxy details as well.
+""" % (name,)
+
+ def helpDescription(self):
+ return "Displays the current server login id."
Added: CalDAVClientLibrary/trunk/src/browser/commands/props.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/commands/props.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/commands/props.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,95 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser.command import Command
+from browser.command import WrongOptions
+from protocol.url import URL
+from browser import utils
+import os
+import getopt
+
+class Cmd(Command):
+
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("props",)
+
+ def execute(self, name, options):
+
+ names = False
+ all = False
+ path = None
+
+ opts, args = getopt.getopt(options.split(), 'an')
+
+ for name, _ignore_value in opts:
+
+ if name == "-a":
+ all = True
+ elif name == "-n":
+ names = True
+ else:
+ print "Unknown option: %s" % (name,)
+ print self.usage(name)
+ raise WrongOptions
+
+ if len(args) > 1:
+ print "Wrong number of arguments: %d" % (len(args),)
+ print self.usage(name)
+ raise WrongOptions
+ elif args:
+ path = args[0]
+ if not path.startswith("/"):
+ path = os.path.join(self.shell.wd, path)
+ else:
+ path = self.shell.wd
+ if not path.endswith("/"):
+ path += "/"
+ resource = URL(url=path)
+
+ if names:
+ results = self.shell.account.session.getPropertyNames(resource)
+ print " Properties: %s" % (utils.printList(results),)
+ else:
+ if all:
+ props = None
+ else:
+ props = self.shell.account.session.getPropertyNames(resource)
+ results, bad = self.shell.account.session.getProperties(resource, props)
+ print "OK Properties:"
+ utils.printProperties(results)
+ if bad:
+ print "Failed Properties:"
+ utils.printProperties(bad)
+
+ return True
+
+ def complete(self, text):
+ return self.shell.wdcomplete(text)
+
+ def usage(self, name):
+ return """Usage: %s [OPTIONS] [PATH]
+PATH is a relative or absolute path.
+
+Options:
+-n list property names only
+-a list all properties via allprop
+ if neither of the above are set then property names are first listed, and then values of those looked up.
+ only one of -n and -a can be set.
+""" % (name,)
+
+ def helpDescription(self):
+ return "List the properties of a directory or file."
Added: CalDAVClientLibrary/trunk/src/browser/commands/proxies.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/commands/proxies.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/commands/proxies.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,245 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser.command import Command
+from browser.command import WrongOptions
+from protocol.url import URL
+from browser import utils
+from browser.subshell import SubShell
+from browser import commands
+import getopt
+
+class Cmd(Command):
+
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("proxies", )
+ self.subshell = None
+
+ def execute(self, name, options):
+
+ interactive = False
+ read = False
+ write = False
+ principal = self.shell.account.getPrincipal()
+
+ try:
+ opts, args = getopt.getopt(options.split(), 'irwp:')
+ except getopt.GetoptError, e:
+ print str(e)
+ print self.usage(name)
+ raise WrongOptions
+
+ for name, value in opts:
+
+ if name == "-i":
+ interactive = True
+ elif name == "-r":
+ read = True
+ elif name == "-w":
+ write = True
+ elif name == "-p":
+ principal = self.shell.account.getPrincipal(URL(url=value))
+ else:
+ print "Unknown option: %s" % (name,)
+ print self.usage(name)
+ raise WrongOptions
+
+ if len(args) > 0:
+ print "Wrong number of arguments: %d" % (len(args),)
+ print self.usage(name)
+ raise WrongOptions
+
+ if interactive:
+ self.doInteractiveMode(principal)
+ else:
+ utils.printProxyPrincipals(self.shell.account, principal, read or not write, write or not read, True)
+
+ return True
+
+ def doInteractiveMode(self, principal):
+
+ print "Entering Proxy edit mode on principal: %s (%s)" % (principal.getSmartDisplayName(), principal.principalURL)
+ if not self.subshell:
+ self.subshell = SubShell(self.shell, "Proxy", (
+ commands.help.Cmd(),
+ commands.logging.Cmd(),
+ commands.quit.Cmd(),
+ Add(),
+ Remove(),
+ List(),
+ ))
+ self.subshell.principal = principal
+ self.subshell.account = self.shell.account
+ self.subshell.run()
+
+ def usage(self, name):
+ return """Usage: %s [OPTIONS]
+PRINCIPAL - principal path to request proxies for.
+
+Options:
+ -i interactive mode for adding, changing and deleting proxies.
+ -r read proxies [OPTIONAL]
+ -w write proxies [OPTIONAL]
+ if neither is present, both are displayed.
+
+ -p principal path to request proxies for [OPTIONAL]
+ if not present, the current user's principal is used.
+""" % (name,)
+
+ def helpDescription(self):
+ return "Displays the delegates for the chosen user."
+
+class CommonProxiesCommand(Command):
+
+ def parseOptions(self, name, options):
+ read = False
+ write = False
+
+ try:
+ opts, args = getopt.getopt(options.split(), 'rw')
+ except getopt.GetoptError, e:
+ print str(e)
+ print self.usage(name)
+ raise WrongOptions
+
+ for name, _ignore_value in opts:
+
+ if name == "-r":
+ read = True
+ if write:
+ print "Only one of -r and -w may be specified."
+ print self.usage(name)
+ raise WrongOptions
+ elif name == "-w":
+ write = True
+ if read:
+ print "Only one of -r and -w may be specified."
+ print self.usage(name)
+ raise WrongOptions
+ else:
+ print "Unknown option: %s" % (name,)
+ print self.usage(name)
+ raise WrongOptions
+
+ if not read and not write:
+ print "One of -r and -w must be specified."
+ print self.usage(name)
+ raise WrongOptions
+
+ if len(args) > 0:
+ print "Wrong number of arguments: %d" % (len(args),)
+ print self.usage(name)
+ raise WrongOptions
+
+ return read
+
+ def printProxyList(self, read):
+
+ if read:
+ principals = self.shell.principal.getReadProxies()
+ else:
+ principals = self.shell.principal.getWriteProxies()
+ if principals:
+ print utils.printPrincipalList(principals)
+ else:
+ print "There are no proxies of the specified type."
+ return principals
+
+class Add(CommonProxiesCommand):
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("add",)
+
+ def execute(self, name, options):
+
+ read = self.parseOptions(name, options)
+
+ principals = self.printProxyList(read)
+
+ choice = utils.textInput("New principal [q - quit]: ")
+ if choice == "q":
+ return None
+ principal = self.shell.account.getPrincipal(URL(url=choice))
+ if principal:
+ principals.append(principal)
+ principals = [principal.principalURL for principal in principals]
+ if read:
+ self.shell.principal.setReadProxies(principals)
+ else:
+ self.shell.principal.setWriteProxies(principals)
+
+ def usage(self, name):
+ return """Usage: %s [OPTIONS]
+
+Options:
+ -r add to read-only proxy list
+ -w add to read-write proxy list
+ one of -r or -w must be present.
+""" % (name,)
+
+ def helpDescription(self):
+ return "Add proxies on principal."
+
+class Remove(CommonProxiesCommand):
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("remove",)
+
+ def execute(self, name, options):
+
+ read = self.parseOptions(name, options)
+
+ principals = self.printProxyList(read)
+
+ choice = utils.numericInput("Select principal: ", 1, len(principals), allow_q=True)
+ if choice == "q":
+ return None
+ del principals[choice-1]
+ principals = [principal.principalURL for principal in principals]
+ if read:
+ self.shell.principal.setReadProxies(principals)
+ else:
+ self.shell.principal.setWriteProxies(principals)
+
+ def usage(self, name):
+ return """Usage: %s [OPTIONS]
+
+Options:
+ -r remove from read-only proxy list
+ -w remove from read-write proxy list
+ one of -r or -w must be present.
+""" % (name,)
+
+ def helpDescription(self):
+ return "Remove proxies on principal."
+
+class List(CommonProxiesCommand):
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("list",)
+
+ def execute(self, name, options):
+
+ utils.printProxyPrincipals(self.shell.account, self.shell.principal, True, True, True, True)
+ return True
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+ def helpDescription(self):
+ return "List current ACLs on existing resource."
Added: CalDAVClientLibrary/trunk/src/browser/commands/quit.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/commands/quit.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/commands/quit.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,37 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser.command import Command
+from browser.command import WrongOptions
+
+class Cmd(Command):
+
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("quit", "exit",)
+
+ def execute(self, name, options):
+ if options:
+ print self.usage(name)
+ raise WrongOptions()
+ raise SystemExit("quitting")
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+ def helpDescription(self):
+ return "Terminates this session."
Added: CalDAVClientLibrary/trunk/src/browser/commands/server.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/commands/server.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/commands/server.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,38 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser.command import Command
+from browser.command import WrongOptions
+
+class Cmd(Command):
+
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("server", )
+
+ def execute(self, name, options):
+ if options:
+ print self.usage(name)
+ raise WrongOptions()
+ print self.shell.server
+ return True
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+ def helpDescription(self):
+ return "Displays the current server."
Added: CalDAVClientLibrary/trunk/src/browser/commands/whoami.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/commands/whoami.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/commands/whoami.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,38 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser.command import Command
+from browser.command import WrongOptions
+
+class Cmd(Command):
+
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("whoami", )
+
+ def execute(self, name, options):
+ if options:
+ print self.usage(name)
+ raise WrongOptions()
+ print self.shell.user
+ return True
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+ def helpDescription(self):
+ return "Displays the current server login id."
Added: CalDAVClientLibrary/trunk/src/browser/shell.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/shell.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/shell.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,110 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser.baseshell import BaseShell
+from browser.command import Command
+from client.account import CalDAVAccount
+from getpass import getpass
+from protocol.url import URL
+import browser.commands
+import atexit
+import getopt
+import sys
+
+class Shell(BaseShell):
+
+ def __init__(self, server, user, pswd, logging):
+
+ super(Shell, self).__init__("caldav_client")
+ self.prefix = self.wd = "/"
+ self.server = server
+ self.user = user
+ self.pswd = pswd
+
+ self.registerCommands()
+
+ # Create the account
+ ssl = server.startswith("https://")
+ server = server[8:] if ssl else server[7:]
+ paths = "/principals/users/%s/" % (self.user,)
+ self.account = CalDAVAccount(server, ssl=ssl, user=self.user, pswd=self.pswd, root=paths, principal=paths, logging=logging)
+
+ atexit.register(self.saveHistory)
+
+ def registerCommands(self):
+ module = browser.commands
+ for item in module.__all__:
+ mod = __import__("browser.commands." + item, globals(), locals(), ["Cmd",])
+ cmd_class = mod.Cmd
+ if type(cmd_class) is type and issubclass(cmd_class, Command):
+ self.registerCommand(cmd_class())
+
+ def setWD(self, newwd):
+
+ # Check that the new one exists
+ resource = (newwd if newwd.endswith("/") else newwd + "/")
+ if not self.account.session.testResource(URL(url=resource)):
+ return False
+ self.prefix = self.wd = newwd
+ return True
+
+def usage():
+ return """Usage: shell [OPTIONS]
+
+Options:
+-l start with HTTP logging on.
+--server=HOST url of the server include http/https scheme and port [REQUIRED].
+--user=USER user name to login as - will be prompted if not prsent [OPTIONAL].
+--pswd=PSWD password for user - will be prompted if not prsent [OPTIONAL].
+"""
+
+def runit():
+ logging = False
+ server = None
+ user = None
+ pswd = None
+
+ opts, _ignore_args = getopt.getopt(sys.argv[1:], 'lh', ["help", "server=", "user=", "pswd="])
+
+ for name, value in opts:
+
+ if name == "-l":
+ logging = True
+ elif name == "--server":
+ server = value
+ elif name == "--user":
+ user = value
+ elif name == "--pswd":
+ pswd = value
+ else:
+ print usage()
+ raise SystemExit()
+
+ if not server or not (server.startswith("http://") or server.startswith("https://")):
+ print usage()
+ raise SystemExit()
+
+ if not user:
+ user = raw_input("User: ")
+ if not pswd:
+ pswd = getpass("Password: ")
+
+ shell = Shell(server, user, pswd, logging)
+ shell.run()
+
+if __name__ == '__main__':
+
+ runit()
Property changes on: CalDAVClientLibrary/trunk/src/browser/shell.py
___________________________________________________________________
Name: svn:executable
+ *
Added: CalDAVClientLibrary/trunk/src/browser/subshell.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/subshell.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/subshell.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,34 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from browser.command import Command
+from browser.baseshell import BaseShell
+
+class SubShell(BaseShell):
+
+ def __init__(self, shell, prefix, cmds):
+
+ super(SubShell, self).__init__("caldav_client.%s" % (prefix,))
+ self.shell = shell
+ self.prefix = prefix
+ self.preserve_history = True
+ self.registerCommands(cmds)
+
+ def registerCommands(self, cmds):
+ for cmd in cmds:
+ if isinstance(cmd, Command):
+ self.registerCommand(cmd)
+
Added: CalDAVClientLibrary/trunk/src/browser/utils.py
===================================================================
--- CalDAVClientLibrary/trunk/src/browser/utils.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/browser/utils.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,188 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.url import URL
+from protocol.webdav.definitions import davxml
+import readline
+import types
+
+
+def printPrincipalPaths(account, principals, resolve, refresh):
+
+ result = ""
+ if resolve:
+ results = [account.getPrincipal(item, refresh=refresh) for item in principals]
+ results.sort(key=lambda x: x.getSmartDisplayName())
+ strlen = reduce(lambda x,y: max(x, len(y.getSmartDisplayName()) + 1), results, 0)
+ results = ["%- *s (%s)" % (strlen, principal.getSmartDisplayName(), principal.principalURL) for principal in results]
+ else:
+ results = [item.relativeURL() for item in principals]
+ results.sort()
+
+ if len(results) == 1:
+ return results[0]
+ else:
+ for item in results:
+ result += "\n %s" % (item,)
+
+ return result
+
+def printPrincipals(account, principals, resolve, refresh):
+
+ result = ""
+ if resolve:
+ results = list(principals)
+ if refresh:
+ for principal in results:
+ principal.loadDetails(True)
+ results.sort(key=lambda x: x.getSmartDisplayName())
+ strlen = reduce(lambda x,y: max(x, len(y.getSmartDisplayName()) + 1), results, 0)
+ results = ["%- *s (%s)" % (strlen, principal.getSmartDisplayName(), principal.principalURL) for principal in results]
+ else:
+ results = [item.principalURL for item in principals]
+ results.sort()
+
+ if len(results) == 1:
+ return results[0]
+ else:
+ for item in results:
+ result += "\n %s" % (item,)
+
+ return result
+
+def printProxyPrincipals(account, principal, read=True, write=True, resolve=True, refresh_main=False, refresh=False):
+ if read:
+ print " Read-Only Proxies: %s" % printPrincipals(account, principal.getReadProxies(refresh_main), resolve, refresh)
+ refresh_main = False
+ if write:
+ print " Read-Write Proxies: %s" % printPrincipals(account, principal.getWriteProxies(refresh_main), resolve, refresh)
+
+def printProperties(items):
+ sorted = items.keys()
+ sorted.sort()
+ for key in sorted:
+ value = items[key]
+ if type(value) in (types.StringType, types.UnicodeType, types.IntType):
+ print " %s: %s" % (key, value,)
+ elif type(value) in (types.ListType, types.TupleType,):
+ print " %s: %s" % (key, printList(value),)
+ else:
+ print " %s: %s" % (key, value,)
+
+def printList(items):
+ result = ""
+ if len(items) == 1:
+ return items[0]
+ else:
+ sorted = list(items)
+ sorted.sort()
+ for item in sorted:
+ result += "\n %s" % (item,)
+ return result
+
+def printTwoColumnList(items, indent=0):
+
+ strlen = reduce(lambda x,y: max(x, len(y[0]) + 1), items, 0)
+ sorted = list(items)
+ sorted.sort(key=lambda x: x[0])
+ for col1, col2 in sorted:
+ print "%s%- *s - %s" % (" "*indent, strlen, col1, col2,)
+
+def printPrincipalList(principals):
+
+ result = ""
+ for ctr, principal in enumerate(principals):
+ result += "\n% 2d. %s (%s)" % (ctr+1, principal.getSmartDisplayName(), principal.principalURL)
+ return result
+
+def printACEList(aces, account):
+
+ result = ""
+ for ctr, ace in enumerate(aces):
+ result += "\n% 2d. %s" % (ctr+1, printACE(ace, account))
+ return result
+
+def printACE(ace, account):
+
+ principalText = ace.principal
+ if principalText == str(davxml.href):
+ principal = account.getPrincipal(URL(url=ace.data))
+ principalText = "%s (%s)" % (principal.getSmartDisplayName(), principal.principalURL)
+ elif principalText == str(davxml.all):
+ principalText = "ALL"
+ elif principalText == str(davxml.authenticated):
+ principalText = "AUTHENTICATED"
+ elif principalText == str(davxml.unauthenticated):
+ principalText = "UNAUTHENTICATED"
+ elif principalText == str(davxml.property):
+ principalText = "PROPERTY: %s" % (ace.data,)
+ result = "Principal: %s\n" % (principalText,)
+ if ace.invert or ace.protected or ace.inherited:
+ result += " Status:"
+ if ace.invert:
+ result += " INVERTED"
+ if ace.protected:
+ result += " PROTECTED"
+ if ace.inherited:
+ result += " INHERITED"
+ result += "\n"
+ result += " Grant: " if ace.grant else " Deny: "
+ result += ", ".join(ace.privs)
+ result += "\n"
+ return result
+
+def textInput(title, insert=None):
+ if insert:
+ title = "%s [%s]:" % (title, insert,)
+ result = raw_input(title)
+ if readline.get_current_history_length():
+ readline.remove_history_item(readline.get_current_history_length() - 1)
+ if not result:
+ result = insert
+ return result
+
+def numericInput(title, low, high, allow_q = False, insert=None):
+ if allow_q:
+ result = choiceInput(title, [str(num) for num in range(low, high+1)] + ["q",], insert)
+ if result != "q":
+ result = int(result)
+ return result
+ else:
+ return int(choiceInput(title, [str(num) for num in range(low, high+1)], insert))
+
+def yesNoInput(title, insert=None):
+ return choiceInput(title, ("y", "n"), insert)
+
+def choiceInput(title, choices, insert=None):
+ while True:
+ result = textInput(title, insert)
+ if not result:
+ continue
+ if result in choices:
+ return result
+ print "Invalid input. Try again."
+
+def multiChoiceInput(title, choices, insert=None):
+ while True:
+ result = textInput(title, insert)
+ if not result:
+ continue
+ for char in result:
+ if char not in choices:
+ break
+ else:
+ return result
+ print "Invalid input. Try again."
Added: CalDAVClientLibrary/trunk/src/client/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/client/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/client/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,16 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
Added: CalDAVClientLibrary/trunk/src/client/account.py
===================================================================
--- CalDAVClientLibrary/trunk/src/client/account.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/client/account.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,32 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from client.clientsession import CalDAVSession
+from client.principal import principalCache
+
+class CalDAVAccount(object):
+
+ def __init__(self, server, port=None, ssl=False, user="", pswd="", principal=None, root=None, logging=False):
+ self.session = CalDAVSession(server, port, ssl, user, pswd, principal, root, logging)
+ self.principal = principalCache.getPrincipal(self.session, self.session.principalPath)
+
+ def getPrincipal(self, path=None, refresh=False):
+ if path:
+ return principalCache.getPrincipal(self.session, path, refresh=refresh)
+ elif refresh:
+ self.principal = principalCache.getPrincipal(self.session, self.session.principalPath, refresh=refresh)
+
+ return self.principal
Added: CalDAVClientLibrary/trunk/src/client/calendar.py
===================================================================
--- CalDAVClientLibrary/trunk/src/client/calendar.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/client/calendar.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,67 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from protocol.webdav.definitions import davxml
+from protocol.url import URL
+from protocol.caldav.definitions import caldavxml
+
+class Calendar(object):
+
+ def __init__(self, path=None, session=None):
+ self.path = path
+ if not path.endswith("/"):
+ self.path += "/"
+ self.session = session
+ self.displayname = None
+ self.description = None
+ self.timezone = None
+
+ def __str__(self):
+ return "Calendar: %s" % (self.path,)
+
+ def __repr__(self):
+ return "Calendar: %s" % (self.path,)
+
+ def exists(self):
+ return self.session.testResource(URL(url=self.path))
+
+ def readCalendar(self):
+ pass
+ def writeCalendar(self, calendar):
+ pass
+
+ def readComponent(self, name=None, uid=None):
+ pass
+ def writeComponent(self, component, name=None):
+ pass
+
+ def getDisplayName(self):
+ if self.displayname is None and self.session:
+ self._getProperties()
+ return self.displayname
+
+ def getDescription(self):
+ if self.description is None and self.session:
+ self._getProperties()
+ return self.description
+
+ def _getProperties(self):
+ assert(self.session is not None)
+
+ results, _ignore_bad = self.session.getProperties(URL(url=self.path), (davxml.displayname, caldavxml.calendar_description, caldavxml.calendar_timezone,))
+ self.displayname = results.get(davxml.displayname, "")
+ self.description = results.get(caldavxml.calendar_description, "")
+ self.timezone = results.get(caldavxml.calendar_timezone, None)
Added: CalDAVClientLibrary/trunk/src/client/calendaruseraddress.py
===================================================================
--- CalDAVClientLibrary/trunk/src/client/calendaruseraddress.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/client/calendaruseraddress.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,45 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+
+class CalendarUserAddress(object):
+
+ def __init__(self, cuaddr=None, name=None, attendee=None):
+ self.cuaddr = cuaddr
+ self.name = name
+ if attendee:
+ self.setAttendee(attendee)
+
+ def getCUAddr(self):
+ return self.cuaddr
+
+ def setCUAddr(self, value):
+ self.cuaddr = value
+
+ def getName(self):
+ return self.name
+
+ def setCn(self, value):
+ self.name = value
+
+ def getFullText(self):
+ return ("%s <%s>" % (self.name, self.cuaddr,)) if self.name else ("<%s>" % (self.cuaddr,))
+
+ def getAttendeeProperty(self):
+ pass
+
+ def setAttendee(self, attendee):
+ pass
Added: CalDAVClientLibrary/trunk/src/client/clientsession.py
===================================================================
--- CalDAVClientLibrary/trunk/src/client/clientsession.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/client/clientsession.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,675 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from protocol.webdav.session import Session
+from protocol.webdav.propfind import PropFind
+from protocol.webdav.definitions import davxml
+from protocol.caldav.definitions import headers
+from protocol.http.data.string import ResponseDataString
+from protocol.url import URL
+from protocol.webdav.definitions import statuscodes
+from protocol.webdav.propfindparser import PropFindParser
+from protocol.webdav.principalmatch import PrincipalMatch
+from protocol.http.authentication.basic import Basic
+from protocol.http.authentication.digest import Digest
+from protocol.webdav.proppatch import PropPatch
+from xml.etree.ElementTree import Element
+from protocol.webdav.get import Get
+from protocol.webdav.propnames import PropNames
+from protocol.webdav.propall import PropAll
+from protocol.webdav.acl import ACL
+import types
+import httplib
+
+class CalDAVSession(Session):
+
+ class logger(object):
+
+ def write(self, data):
+ print data.replace("\r\n", "\n"),
+
+ def __init__(self, server, port=None, ssl=False, user="", pswd="", principal=None, root=None, logging=False):
+ super(CalDAVSession, self).__init__(server, port, ssl, log=CalDAVSession.logger())
+
+ self.loghttp = logging
+
+ self.user = user
+ self.pswd = pswd
+
+ # Initialize state
+ self.connect = None
+
+ # Paths
+ self.rootPath = URL(url=root)
+ self.principalPath = URL(url=principal)
+
+ self._initCalDAVState()
+
+ def _initCalDAVState(self):
+
+ # We need to cache the server capabilities and properties
+ if not self.principalPath:
+ self._discoverPrincipal()
+
+ def _discoverPrincipal(self):
+
+ hrefs = self.getHrefListProperty(self.rootPath, davxml.principal_collection_set)
+ if not hrefs:
+ return
+
+ # For each principal collection find one that matches self
+ for href in hrefs:
+
+ results = self.getSelfHrefs(href)
+ if results:
+ self.principalPath = results[0]
+ if self.log:
+ self.log.write("Found principal path: %s" % (self.principalPath.absoluteURL(),))
+ return
+
+ def testResource(self, rurl):
+
+ assert(isinstance(rurl, URL))
+
+ request = PropFind(self, rurl.relativeURL(), headers.Depth0, (davxml.resourcetype,))
+
+ # Process it
+ self.runSession(request)
+
+ return request.getStatusCode() == statuscodes.MultiStatus
+
+ def getPropertyNames(self, rurl):
+
+ assert(isinstance(rurl, URL))
+
+ results = ()
+
+ # Create WebDAV propfind
+ request = PropNames(self, rurl.relativeURL(), headers.Depth0)
+ result = ResponseDataString()
+ request.setOutput(result)
+
+ # Process it
+ self.runSession(request)
+
+ # If its a 207 we want to parse the XML
+ if request.getStatusCode() == statuscodes.MultiStatus:
+
+ parser = PropFindParser()
+ parser.parseData(result.getData())
+
+ # Look at each propfind result
+ for item in parser.getResults().itervalues():
+
+ # Get child element name (decode URL)
+ name = URL(url=item.getResource(), decode=True)
+
+ # Must match rurl
+ if name.equalRelative(rurl):
+
+ results = tuple([name for name in item.getNodeProperties().iterkeys()])
+
+ else:
+ self.handleHTTPError(request)
+
+ return results
+
+ def getProperties(self, rurl, props):
+
+ assert(isinstance(rurl, URL))
+
+ results = {}
+ bad = None
+
+ # Create WebDAV propfind
+ if props:
+ request = PropFind(self, rurl.relativeURL(), headers.Depth0, props)
+ else:
+ request = PropAll(self, rurl.relativeURL(), headers.Depth0)
+ result = ResponseDataString()
+ request.setOutput(result)
+
+ # Process it
+ self.runSession(request)
+
+ # If its a 207 we want to parse the XML
+ if request.getStatusCode() == statuscodes.MultiStatus:
+
+ parser = PropFindParser()
+ parser.parseData(result.getData())
+
+ # Look at each propfind result
+ for item in parser.getResults().itervalues():
+
+ # Get child element name (decode URL)
+ name = URL(url=item.getResource(), decode=True)
+
+ # Must match rurl
+ if name.equalRelative(rurl):
+ for name, value in item.getTextProperties().iteritems():
+ results[name] = value
+ for name, value in item.getHrefProperties().iteritems():
+ if name not in results:
+ results[name] = value
+ for name, value in item.getNodeProperties().iteritems():
+ if name not in results:
+ results[name] = value
+ bad = item.getBadProperties()
+ else:
+ self.handleHTTPError(request)
+
+ return results, bad
+
+ def getPropertiesOnHierarchy(self, rurl, props):
+
+ assert(isinstance(rurl, URL))
+
+ results = {}
+
+ # Create WebDAV propfind
+ request = PropFind(self, rurl.relativeURL(), headers.Depth1, props)
+ result = ResponseDataString()
+ request.setOutput(result)
+
+ # Process it
+ self.runSession(request)
+
+ # If its a 207 we want to parse the XML
+ if request.getStatusCode() == statuscodes.MultiStatus:
+
+ parser = PropFindParser()
+ parser.parseData(result.getData())
+
+ # Look at each propfind result
+ for item in parser.getResults().itervalues():
+
+ # Get child element name (decode URL)
+ name = URL(url=item.getResource(), decode=True)
+ propresults = {}
+ results[name.relativeURL()] = propresults
+
+ for prop in props:
+
+ if item.getTextProperties().has_key(str(prop)):
+ propresults[prop] = item.getTextProperties().get(str(prop))
+
+ elif item.getNodeProperties().has_key(str(prop)):
+ propresults[prop] = item.getNodeProperties()[str(prop)]
+ else:
+ self.handleHTTPError(request)
+
+ return results
+
+ def getHrefListProperty(self, rurl, propname):
+
+ assert(isinstance(rurl, URL))
+
+ results = ()
+
+ # Create WebDAV propfind
+ request = PropFind(self, rurl.relativeURL(), headers.Depth0, (propname,))
+ result = ResponseDataString()
+ request.setOutput(result)
+
+ # Process it
+ self.runSession(request)
+
+ # If its a 207 we want to parse the XML
+ if request.getStatusCode() == statuscodes.MultiStatus:
+
+ parser = PropFindParser()
+ parser.parseData(result.getData())
+
+ # Look at each propfind result and extract any Hrefs
+ for item in parser.getResults().itervalues():
+
+ # Get child element name (decode URL)
+ name = URL(url=item.getResource(), decode=True)
+
+ # Must match rurl
+ if name.equalRelative(rurl):
+
+ if item.getNodeProperties().has_key(str(propname)):
+
+ propnode = item.getNodeProperties()[str(propname)]
+ results += tuple([URL(url=href.text, decode=True) for href in propnode.findall(str(davxml.href)) if href.text])
+ else:
+ self.handleHTTPError(request)
+
+ return results
+
+ # Do principal-match report with self on the passed in url
+ def getSelfProperties(self, rurl, props):
+
+ assert(isinstance(rurl, URL))
+
+ results = {}
+
+ # Create WebDAV principal-match
+ request = PrincipalMatch(self, rurl.relativeURL(), props)
+ result = ResponseDataString()
+ request.setOutput(result)
+
+ # Process it
+ self.runSession(request)
+
+ # If its a 207 we want to parse the XML
+ if request.getStatusCode() == statuscodes.MultiStatus:
+
+ parser = PropFindParser()
+ parser.parseData(result.getData())
+
+ # Look at each principal-match result and return first one that is appropriate
+ for item in parser.getResults().itervalues():
+
+ for prop in props:
+
+ if item.getNodeProperties().has_key(str(prop)):
+
+ href = item.getNodeProperties()[str(prop)].find(str(davxml.href))
+
+ if href is not None:
+ results[prop] = URL(url=href.text, decode=True)
+
+ # We'll take the first one, whatever that is
+ break
+
+ else:
+ self.handleHTTPError(request)
+ return None
+
+ return results
+
+ # Do principal-match report with self on the passed in url
+ def getSelfHrefs(self, rurl):
+
+ assert(isinstance(rurl, URL))
+
+ results = ()
+
+ # Create WebDAV principal-match
+ request = PrincipalMatch(self, rurl.realtiveURL(), (davxml.principal_URL,))
+ result = ResponseDataString()
+ request.setOutput(result)
+
+ # Process it
+ self.runSession(request)
+
+ # If its a 207 we want to parse the XML
+ if request.getStatusCode() == statuscodes.MultiStatus:
+
+ parser = PropFindParser()
+ parser.parseData(result.getData())
+
+ # Look at each propfind result and extract any Hrefs
+ for item in parser.getResults().itervalues():
+
+ # Get child element name (decode URL)
+ name = URL(url=item.getResource(), decode=True)
+ results += (name.path,)
+
+ else:
+ self.handleHTTPError(request)
+ return None
+
+ return results
+
+ # Do principal-match report with self on the passed in url
+ def getSelfPrincipalResource(self, rurl):
+
+ assert(isinstance(rurl, URL))
+
+ hrefs = self.getHrefListProperty(rurl, davxml.principal_collection_set)
+ if not hrefs:
+ return None
+
+ # For each principal collection find one that matches self
+ for href in hrefs:
+
+ results = self.getSelfHrefs(href)
+ if results:
+ return results[0]
+
+ return None
+
+ def setProperties(self, rurl, props):
+
+ assert(isinstance(rurl, URL))
+
+ results = ()
+
+ # Convert property data into something sensible
+ converted = []
+ for name, value in props:
+ node = None
+ if isinstance(value, types.StringType):
+ node = Element(name)
+ node.text = value
+ elif isinstance(value, URL):
+ node = Element(davxml.href)
+ node.text = value.absoluteURL()
+ elif isinstance(value, types.ListType) or isinstance(value, types.TupleType):
+ hrefs = []
+ for item in value:
+ if isinstance(item, URL):
+ href = Element(davxml.href)
+ href.text = item.relativeURL()
+ hrefs.append(href)
+ else:
+ break
+ else:
+ node = Element(name)
+ map(node.append, hrefs)
+
+ if node is not None:
+ converted.append(node)
+
+ # Create WebDAV propfind
+ request = PropPatch(self, rurl.relativeURL(), converted)
+ result = ResponseDataString()
+ request.setOutput(result)
+
+ # Process it
+ self.runSession(request)
+
+ # If its a 207 we want to parse the XML
+ if request.getStatusCode() == statuscodes.MultiStatus:
+
+ parser = PropFindParser()
+ parser.parseData(result.getData())
+
+ # Look at each propfind result
+ for item in parser.getResults().itervalues():
+
+ # Get child element name (decode URL)
+ name = URL(url=item.getResource(), decode=True)
+
+ # Must match rurl
+ if name.equalRelative(rurl):
+
+ for prop in item.getNodeProperties():
+ results += (prop,)
+
+ else:
+ self.handleHTTPError(request)
+
+ return results
+
+ def setACL(self, rurl, aces):
+
+ assert(isinstance(rurl, URL))
+
+ # Create WebDAV ACL
+ request = ACL(self, rurl.relativeURL(), aces)
+
+ # Process it
+ self.runSession(request)
+
+ if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent):
+ self.handleHTTPError(request)
+
+ def readData(self, rurl):
+
+ assert(isinstance(rurl, URL))
+
+ # Create WebDAV GET
+ request = Get(self, rurl.relativeURL())
+ dout = ResponseDataString()
+ request.setData(dout)
+
+ # Process it
+ self.runSession(request)
+
+ # Check response status
+ if request.getStatusCode() != statuscodes.OK:
+ self.handleHTTPError(request)
+ return None
+
+ # Look for ETag
+ if request.getNewETag() is not None:
+
+ etag = request.getNewETag()
+
+ # Handle server bug: ETag value MUST be quoted per HTTP/1.1 S3.11
+ if not etag.startswith('"'):
+ etag = "\"%s\"" % (etag,)
+ else:
+ etag = None
+
+ # Return data as a string and etag
+ return dout.getData(), etag
+
+ def openSession(self):
+ # Create connection
+ if self.ssl:
+ self.connect = httplib.HTTPSConnection(self.server, self.port)
+ else:
+ self.connect = httplib.HTTPConnection(self.server, self.port)
+ self.connect.connect()
+
+ # Write to log file
+ if self.loghttp and self.log:
+ self.log.write("\n <-------- BEGIN HTTP CONNECTION -------->\n")
+ self.log.write("Server: %s\n" % (self.server,))
+
+ def closeSession(self):
+ if self.connect:
+ self.connect.close()
+ self.connect = None
+
+ # Write to log file
+ if self.loghttp and self.log:
+ self.log.write("\n <-------- END HTTP CONNECTION -------->\n")
+
+ def runSession(self, request):
+
+ ctr = 5
+ while ctr:
+ ctr -= 1
+
+ self.doSession(request)
+
+ if request and request.isRedirect():
+ location = request.getResponseHeader(headers.Location)
+ if location:
+ u = URL(location)
+ if not u.scheme or u.scheme in ("http", "https",):
+ # Get new server and base RURL
+ different_server = (self.server != u.server) if u.server else False
+
+ # Use new host in this session
+ if different_server:
+ self.setServer(u.server)
+
+ # Reset the request with new info
+ request.setURL(u.relativeURL())
+ request.clearResponse()
+
+ # Write to log file
+ if self.loghttp and self.log:
+ self.log.write("\n <-------- HTTP REDIRECT -------->\n")
+ self.log.write("Location: %s\n" % (location,))
+
+ # Recyle through loop
+ continue
+
+ # Exit when redirect does not occur
+ break
+
+ def doSession(self, request):
+ # Do initialisation if not already done
+ if not self.initialised:
+
+ if not self.initialise(self.server, self.rootPath.relativeURL()):
+
+ # Break connection with server
+ self.closeConnection()
+ return
+
+ # Do the request if present
+ if request:
+
+ # Handle delayed authorization
+ first_time = True
+ while True:
+
+ # Run the request actions - this will make any connection that is needed
+ self.sendRequest(request)
+
+ # Check for auth failure if none before
+ if request.getStatusCode() == statuscodes.Unauthorized:
+
+ # If we had authorization before, then chances are auth details are wrong - so delete and try again with new auth
+ if self.hasAuthorization():
+
+ self.authorization = None
+
+ # Display error so user knows why the prompt occurs again - but not the first time
+ # as we might have a digest re-auth.
+ if not first_time:
+ self.displayHTTPError(request)
+
+
+ # Get authorization object (prompt the user) and redo the request
+ self.authorization, cancelled = self.getAuthorizor(first_time, request.getResponseHeaders(headers.WWWAuthenticate))
+
+ # Check for auth cancellation
+ if cancelled:
+ self.authorization = None
+
+ else:
+ first_time = False
+
+ request.clearResponse()
+
+ # Repeat the request loop with new authorization
+ continue
+
+ # If we get here we are complete with auth loop
+ break
+
+ # Now close it - eventually we will do keep-alive support
+
+ # Break connection with server
+ self.closeConnection()
+
+ def doRequest(self, request):
+
+ # Write request headers
+ self.connect.putrequest(request.method, request.url, skip_host=True, skip_accept_encoding=True)
+ hdrs = request.getRequestHeaders()
+ for header, value in hdrs:
+ self.connect.putheader(header, value)
+ self.connect.endheaders()
+
+ # Write to log file
+ if self.loghttp and self.log:
+ self.log.write("\n <-------- BEGIN HTTP REQUEST -------->\n")
+ self.log.write("%s\n" % (request.getRequestStartLine(),))
+ for header, value in hdrs:
+ self.log.write("%s: %s\n" % (header, value))
+ self.log.write("\n")
+
+ # Write the data
+ self.writeRequestData(request)
+
+ # Blank line in log between
+ if self.loghttp and self.log:
+ self.log.write("\n <-------- BEGIN HTTP RESPONSE -------->\n")
+
+ # Get response
+ response = self.connect.getresponse()
+
+ # Get response headers
+ request.setResponseStatus(response.version, response.status, response.reason)
+ request.setResponseHeaders(response.msg.headers)
+ if self.loghttp and self.log:
+ self.log.write("HTTP/%s %s %s\r\n" % (
+ {11:"1.1",10:"1.0",9:"0.9"}.get(response.version, "?"),
+ response.status,
+ response.reason
+ ))
+ for hdr in response.msg.headers:
+ self.log.write(hdr)
+
+ # Now get the data
+ self.readResponseData(request, response)
+
+ # Trailer in log
+ if self.loghttp and self.log:
+ self.log.write("\n <-------- END HTTP RESPONSE -------->\n")
+
+ def handleHTTPError(self, request):
+ print "Ignoring error"
+
+ def getAuthorizor(self, first_time, wwwhdrs):
+
+ for item in wwwhdrs:
+ if item.lower().startswith("basic"):
+ return Basic(self.user, self.pswd), False
+ elif item.lower().startswith("digest"):
+ return Digest(self.user, self.pswd, wwwhdrs), False
+ else:
+ return None, True
+
+ def writeRequestData(self, request):
+
+ # Write the data if any present
+ if request.hasRequestData():
+
+ stream = request.getRequestData()
+ if stream:
+ # Tell data we are using it
+ stream.start()
+
+ # Buffered write from stream
+ more = True
+ while more:
+ data, more = stream.read()
+ if data:
+ self.connect.send(data)
+
+ if self.loghttp and self.log:
+ self.log.write(data)
+
+ # Tell data we are done using it
+ stream.stop()
+
+ def readResponseData(self, request, response):
+
+ # Check for data and write it
+ data = response.read()
+
+ if request.hasResponseData():
+ stream = request.getResponseData()
+ stream.start()
+ stream.write(data)
+ stream.stop()
+ else:
+ response.read()
+
+ if self.loghttp and self.log:
+ self.log.write(data)
+
+ def setServerType(self, type):
+ self.type = type
+
+ def setServerDescriptor(self, txt):
+ self.descriptor = txt
+
+ def setServerCapability(self, txt):
+ self.capability = txt
+
Added: CalDAVClientLibrary/trunk/src/client/fullclient.py
===================================================================
--- CalDAVClientLibrary/trunk/src/client/fullclient.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/client/fullclient.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,46 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from client.account import CalDAVAccount
+
+if __name__ == '__main__':
+ account = CalDAVAccount("", ssl=True, user="", pswd="", root="", principal="")
+
+ print account.getPrincipal()
+
+# memberships = [CalDAVPrincipal(account.session, path) for path in account.getPrincipal().memberships]
+# for member in memberships:
+# member.loadDetails()
+# memberships = [member.displayname for member in memberships]
+# print "Memberships: %s" % (memberships,)
+
+# calendars = account.getPrincipal().listCalendars()
+# for calendar in calendars:
+# print "%s:" % (calendar,)
+# txt = calendar.getDisplayName()
+# if txt:
+# print " Display Name: %s" % (txt,)
+# txt = calendar.getDescription()
+# if txt:
+# print " Description: %s" % (txt,)
+
+# fbset = account.getPrincipal().listFreeBusySet()
+# print fbset
+# account.getPrincipal().cleanFreeBusySet()
+
+ proxies = account.getPrincipal().getReadProxies()
+ for proxy in proxies:
+ print proxy
Added: CalDAVClientLibrary/trunk/src/client/principal.py
===================================================================
--- CalDAVClientLibrary/trunk/src/client/principal.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/client/principal.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,227 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from protocol.caldav.definitions import caldavxml
+from client.calendar import Calendar
+from protocol.url import URL
+from protocol.webdav.definitions import davxml
+from protocol.caldav.definitions import headers
+import types
+
+
+class PrincipalCache(object):
+
+ def __init__(self):
+ self.cache = {}
+
+ def getPrincipal(self, session, path, refresh=False):
+ if path.toString() not in self.cache:
+ principal = CalDAVPrincipal(session, path)
+ principal.loadDetails()
+ self.cache[path.toString()] = principal
+ self.cache[principal.principalURL.toString()] = principal
+ for uri in principal.alternateURIs:
+ self.cache[uri.toString()] = principal
+ elif refresh:
+ self.cache[path.toString()].loadDetails(refresh=True)
+ return self.cache[path.toString()]
+
+principalCache = PrincipalCache()
+
+class CalDAVPrincipal(object):
+
+ def __init__(self, session, path):
+
+ self.session = session
+ self.principalPath = path
+ self._initFields()
+
+ def __str__(self):
+ return """
+ Principal Path : %s
+ Display Name : %s
+ Principal URL : %s
+ Alternate URLs : %s
+ Group Members : %s
+ Memberships : %s
+ Calendar Homes : %s
+ Outbox URL : %s
+ Inbox URL : %s
+ Calendar Addresses: %s
+""" % (
+ self.principalPath,
+ self.displayname,
+ self.principalURL,
+ self.alternateURIs,
+ self.memberset,
+ self.memberships,
+ self.homeset,
+ self.outboxURL,
+ self.inboxURL,
+ self.cuaddrs
+ )
+
+ def _initFields(self):
+ self.loaded = False
+ self.valid = False
+ self.displayname = "Invalid Principal"
+ self.principalURL = ""
+ self.alternateURIs = ()
+ self.memberset = ()
+ self.memberships = ()
+ self.homeset = ()
+ self.outboxURL = ""
+ self.inboxURL = ""
+ self.cuaddrs = ()
+
+ self.proxyFor = None
+ self.proxyreadURL = ""
+ self.proxywriteURL = ""
+
+ def loadDetails(self, refresh=False):
+ if self.loaded and not refresh:
+ return
+ self._initFields()
+
+ results, _ignore_bad = self.session.getProperties(
+ self.principalPath,
+ (
+ davxml.resourcetype,
+ davxml.displayname,
+ davxml.principal_URL,
+ davxml.alternate_URI_set,
+ davxml.group_member_set,
+ davxml.group_membership,
+ caldavxml.calendar_home_set,
+ caldavxml.schedule_outbox_URL,
+ caldavxml.schedule_inbox_URL,
+ caldavxml.calendar_user_address_set,
+ ),
+ )
+ if results:
+ # First check that we have a valid principal and see if its a proxy principal too
+ type = results.get(davxml.resourcetype, None)
+ self.valid = type.find(str(davxml.principal)) is not None
+ if (self.session.hasDAVVersion(headers.calendar_proxy) and
+ (type.find(str(caldavxml.calendar_proxy_read)) is not None or
+ type.find(str(caldavxml.calendar_proxy_write)) is not None)):
+ parentPath = self.principalPath.dirname()
+ self.proxyFor = principalCache.getPrincipal(self.session, parentPath, refresh)
+
+ if self.valid:
+ self.displayname = results.get(davxml.displayname, None)
+ self.principalURL = results.get(davxml.principal_URL, None)
+ self.alternateURIs = results.get(davxml.alternate_URI_set, None)
+ self.memberset = results.get(davxml.group_member_set, None)
+ self.memberships = results.get(davxml.group_membership, None)
+ self.homeset = results.get(caldavxml.calendar_home_set, None)
+ self.outboxURL = results.get(caldavxml.schedule_outbox_URL, None)
+ self.inboxURL = results.get(caldavxml.schedule_inbox_URL, None)
+ self.cuaddrs = results.get(caldavxml.calendar_user_address_set, None)
+
+ # Get proxy resource details if proxy support is available
+ if self.session.hasDAVVersion(headers.calendar_proxy) and not self.proxyFor:
+ results = self.session.getPropertiesOnHierarchy(self.principalPath, (davxml.resourcetype,))
+ for path, items in results.iteritems():
+ if davxml.resourcetype in items:
+ rtype = items[davxml.resourcetype]
+ if rtype.find(str(caldavxml.calendar_proxy_read)) is not None:
+ self.proxyreadURL = URL(url=path)
+ elif rtype.find(str(caldavxml.calendar_proxy_write)) is not None:
+ self.proxywriteURL = URL(url=path)
+
+ self.loaded = True
+
+ def getSmartDisplayName(self):
+ if self.proxyFor:
+ return "%s#%s" % (self.proxyFor.displayname, self.displayname,)
+ else:
+ return self.displayname
+
+ def listCalendars(self, root=None):
+ calendars = []
+ home = self.homeset[0] if type(self.homeset) in (types.TupleType,) else self.homeset
+ if not home.path.endswith("/"):
+ home.path += "/"
+
+ results = self.session.getPropertiesOnHierarchy(home, (davxml.resourcetype,))
+ for path, items in results.iteritems():
+ if davxml.resourcetype in items:
+ rtype = items[davxml.resourcetype]
+ if rtype.find(str(davxml.collection)) is not None and rtype.find(str(caldavxml.calendar)) is not None:
+ calendars.append(Calendar(path=path, session=self.session))
+ return calendars
+
+ def listFreeBusySet(self):
+ return self._getFreeBusySet()
+
+ def addToFreeBusySet(self, calendars):
+ current = self._getFreeBusySet()
+ for calendar in calendars:
+ current.append(calendar)
+ self._setFreeBusySet(current)
+
+ def removeFromFreeBusySet(self, calendars):
+ calendar_paths = [calendar.path for calendar in calendars]
+ current = self._getFreeBusySet()
+ current = [cal for cal in current if cal.path not in calendar_paths]
+ self._setFreeBusySet(current)
+
+ def cleanFreeBusySet(self):
+ fbset = self.listFreeBusySet()
+ badfbset = []
+ for calendar in fbset:
+ if not calendar.exists():
+ badfbset.append(calendar)
+
+ if badfbset:
+ self.removeFromFreeBusySet(badfbset)
+
+ def _getFreeBusySet(self):
+ hrefs = self.session.getHrefListProperty(self.inboxURL, caldavxml.calendar_free_busy_set)
+ return [Calendar(href.relativeURL(), session=self.session) for href in hrefs]
+
+ def _setFreeBusySet(self, calendars):
+ hrefs = [URL(url=calendar.path) for calendar in calendars]
+ self.session.setProperties(self.inboxURL, ((caldavxml.calendar_free_busy_set, hrefs),))
+
+ def getReadProxies(self, refresh=True):
+ if not self.proxyreadURL:
+ return ()
+
+ principal = principalCache.getPrincipal(self.session, self.proxyreadURL, refresh=refresh)
+ return [principalCache.getPrincipal(self.session, member) for member in principal.memberset]
+
+
+ def setReadProxies(self, principals):
+ if not self.proxyreadURL:
+ return ()
+
+ self.session.setProperties(self.proxyreadURL, ((davxml.group_member_set, principals),))
+
+ def getWriteProxies(self, refresh=True):
+ if not self.proxywriteURL:
+ return ()
+
+ principal = principalCache.getPrincipal(self.session, self.proxywriteURL, refresh=refresh)
+ return [principalCache.getPrincipal(self.session, member) for member in principal.memberset]
+
+ def setWriteProxies(self, principals):
+ if not self.proxywriteURL:
+ return ()
+
+ self.session.setProperties(self.proxywriteURL, ((davxml.group_member_set, principals),))
+
Added: CalDAVClientLibrary/trunk/src/client/simple.py
===================================================================
--- CalDAVClientLibrary/trunk/src/client/simple.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/client/simple.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,61 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from protocol.webdav.session import Session
+from protocol.webdav.options import Options
+import httplib
+
+def run(session, request):
+
+ # Create connection
+ if session.ssl:
+ connect = httplib.HTTPSConnection(session.server, session.port)
+ else:
+ connect = httplib.HTTPConnection(session.server, session.port)
+ connect.set_debuglevel(1)
+
+ # Do headers
+ connect.putrequest(request.method, request.url, skip_host=True, skip_accept_encoding=True)
+ hdrs = request.getRequestHeaders()
+ for header, value in hdrs.iteritems():
+ connect.putheader(header, value)
+ connect.endheaders()
+
+ # Do request body
+ stream = request.getRequestDataStream()
+ if stream:
+ stream.start()
+ more = True
+ while more:
+ data, more = stream.read()
+ if data:
+ connect.send(data)
+ stream.stop()
+
+ # Get response
+ response = connect.getresponse()
+
+ # Get response headers
+ request.setResponseStatus(response.version, response.status, response.reason)
+ request.setResponseHeaders(response.getheaders())
+
+ # Get response body
+
+if __name__ == '__main__':
+ session = Session("www.mulberrymail.com")
+ request = Options(session, "/")
+
+ run(session, request)
Added: CalDAVClientLibrary/trunk/src/protocol/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2007-2008 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.
+##
Added: CalDAVClientLibrary/trunk/src/protocol/caldav/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/caldav/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/caldav/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,28 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.propfindparser import PropFindParser
+from protocol.webdav.definitions import davxml
+from protocol.caldav.definitions import caldavxml
+
+PropFindParser.textProperties.add(caldavxml.calendar_description)
+PropFindParser.textProperties.add(caldavxml.calendar_timezone)
+
+PropFindParser.hrefListProperties.add(caldavxml.calendar_home_set)
+PropFindParser.hrefListProperties.add(caldavxml.calendar_user_address_set)
+PropFindParser.hrefListProperties.add(caldavxml.calendar_free_busy_set)
+PropFindParser.hrefProperties.add(caldavxml.schedule_outbox_URL)
+PropFindParser.hrefProperties.add(caldavxml.schedule_inbox_URL)
Added: CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2007-2008 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.
+##
Added: CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/caldavxml.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/caldavxml.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/caldavxml.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,83 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from xml.etree.ElementTree import QName
+
+CalDAVNamespace = "urn:ietf:params:xml:ns:caldav"
+
+# RFC4791
+
+mkcalendar = QName(CalDAVNamespace, "mkcalendar")
+mkcalendar_response = QName(CalDAVNamespace, "mkcalendar-response")
+
+calendar = QName(CalDAVNamespace, "calendar")
+
+calendar_description = QName(CalDAVNamespace, "calendar-description")
+calendar_timezone = QName(CalDAVNamespace, "calendar-timezone")
+supported_calendar_component_set = QName(CalDAVNamespace, "supported-calendar-component-set")
+supported_calendar_data = QName(CalDAVNamespace, "supported-calendar-data")
+max_resource_size = QName(CalDAVNamespace, "max-resource-size")
+min_date_time = QName(CalDAVNamespace, "min-date-time")
+max_date_time = QName(CalDAVNamespace, "max-date-time")
+max_instances = QName(CalDAVNamespace, "max-instances")
+max_attendees_per_instance = QName(CalDAVNamespace, "max-attendees-per-instance")
+
+read_free_busy = QName(CalDAVNamespace, "read-free-busy")
+calendar_home_set = QName(CalDAVNamespace, "calendar-home-set")
+
+supported_collation = QName(CalDAVNamespace, "supported-collation")
+
+calendar_query = QName(CalDAVNamespace, "calendar-query")
+calendar_data = QName(CalDAVNamespace, "calendar-data")
+comp = QName(CalDAVNamespace, "comp")
+allcomp = QName(CalDAVNamespace, "allcomp")
+prop = QName(CalDAVNamespace, "prop")
+expand = QName(CalDAVNamespace, "expand")
+limit_recurrence_set = QName(CalDAVNamespace, "limit-recurrence-set")
+limit_freebusy_set = QName(CalDAVNamespace, "limit-freebusy-set")
+filter = QName(CalDAVNamespace, "filter")
+comp_filter = QName(CalDAVNamespace, "comp-filter")
+prop_filter = QName(CalDAVNamespace, "prop-filter")
+param_filter = QName(CalDAVNamespace, "param-filter")
+is_not_defined = QName(CalDAVNamespace, "is-not-defined")
+text_match = QName(CalDAVNamespace, "text-match")
+timezone = QName(CalDAVNamespace, "timezone")
+time_range = QName(CalDAVNamespace, "time-range")
+
+calendar_multiget = QName(CalDAVNamespace, "calendar-multiget")
+
+free_busy_query = QName(CalDAVNamespace, "free-busy-query")
+
+# draft caldav-schedule
+calendar_free_busy_set = QName(CalDAVNamespace, "calendar-free-busy-set")
+originator = QName(CalDAVNamespace, "originator")
+recipient = QName(CalDAVNamespace, "recipient")
+schedule = QName(CalDAVNamespace, "schedule")
+
+schedule_inbox_URL = QName(CalDAVNamespace, "schedule-inbox-URL")
+schedule_outbox_URL = QName(CalDAVNamespace, "schedule-outbox-URL")
+calendar_user_address_set = QName(CalDAVNamespace, "calendar-user-address-set")
+
+schedule_response = QName(CalDAVNamespace, "schedule-response")
+response = QName(CalDAVNamespace, "timezone")
+request_status = QName(CalDAVNamespace, "request-status")
+
+# Extensions
+
+CalendarServerNamespace = "http://calendarserver.org/ns/"
+
+calendar_proxy_read = QName(CalendarServerNamespace, "calendar-proxy-read")
+calendar_proxy_write = QName(CalendarServerNamespace, "calendar-proxy-write")
Added: CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/headers.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/headers.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/headers.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,30 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.definitions.headers import * #@UnusedWildImport
+
+# RFC4791
+CalendarAccess = "calendar-access"
+
+# draft caldav-schedule
+CalendarSchedule = "calendar-schedule"
+
+Originator = "Originator"
+Recipient = "Recipient"
+
+# Extensions
+
+calendar_proxy = "calendar-proxy"
Added: CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/methods.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/methods.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/caldav/definitions/methods.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,21 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.definitions.methods import * #@UnusedWildImport
+
+# RFC4791 - CalDAV Request Methods
+
+MKCALENDAR = "MKCALENDAR";
Added: CalDAVClientLibrary/trunk/src/protocol/caldav/makecalendar.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/caldav/makecalendar.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/caldav/makecalendar.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,76 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.requestresponse import RequestResponse
+from protocol.caldav.definitions import methods
+from StringIO import StringIO
+from protocol.http.data.string import RequestDataString
+from xml.etree.ElementTree import Element
+from protocol.caldav.definitions import caldavxml
+from xml.etree.ElementTree import SubElement
+from protocol.webdav.definitions import davxml
+from protocol.utils.xmlhelpers import BetterElementTree
+
+class MakeCalendar(RequestResponse):
+
+ def __init__(self, session, url, displayname=None, description=None, timezone=None):
+ super(MakeCalendar, self).__init__(session, methods.MKCALENDAR, url)
+ self.displayname = displayname
+ self.description = description
+ self.timezone = timezone
+
+ self.initRequestData()
+
+ def initRequestData(self):
+ if self.displayname or self.description or self.timezone:
+ # Write XML info to a string
+ os = StringIO()
+ self.generateXML(os)
+ self.request_data = RequestDataString(os.getvalue(), "text/xml charset=utf-8")
+
+ def generateXML(self, os):
+ # Structure of document is:
+ #
+ # <CALDAV:mkcalendar>
+ # <DAV:prop>
+ # <<each property as elements>>
+ # </DAV:prop>
+ # </CALDAV:mkcalendar>
+
+ # <CALDAV:mkcalendar> element
+ mkcalendar = Element(caldavxml.mkcalendar)
+
+ # <DAV:prop> element
+ prop = SubElement(mkcalendar, davxml.prop)
+
+ # <DAV:displayname> element
+ if self.displayname:
+ displayname = SubElement(prop, davxml.displayname)
+ displayname.text = self.displayname
+
+ # <CalDAV:calendar-description> element
+ if self.description:
+ description = SubElement(prop, caldavxml.calendar_description)
+ description.text = self.description
+
+ # <CalDAV:timezone> element
+ if self.timezone:
+ timezone = SubElement(prop, caldavxml.calendar_timezone)
+ timezone.text = self.timezone
+
+ # Now we have the complete document, so write it out (no indentation)
+ xmldoc = BetterElementTree(mkcalendar)
+ xmldoc.writeUTF8(os)
Added: CalDAVClientLibrary/trunk/src/protocol/caldav/multiget.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/caldav/multiget.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/caldav/multiget.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,71 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.report import Report
+from StringIO import StringIO
+from protocol.http.data.string import RequestDataString
+from protocol.caldav.definitions import caldavxml
+from xml.etree.ElementTree import Element
+from xml.etree.ElementTree import SubElement
+from protocol.webdav.definitions import davxml
+from protocol.utils.xmlhelpers import BetterElementTree
+
+class Multiget(Report):
+
+ def __init__(self, session, url, hrefs, props=()):
+ super(Multiget, self).__init__(session, url)
+ self.props = props
+ self.hrefs = hrefs
+ pass
+
+ def initRequestData(self):
+ if self.displayname or self.description or self.timezone:
+ # Write XML info to a string
+ os = StringIO()
+ self.generateXML(os)
+ self.request_data = RequestDataString(os.getvalue(), "text/xml charset=utf-8")
+
+ def generateXML(self, os):
+ # Structure of document is:
+ #
+ # <CalDAV:calendar-multiget>
+ # <DAV:prop>
+ # <<names of each property as elements>>
+ # </DAV:prop>
+ # <DAV:href>...</DAV:href>
+ # ...
+ # </CalDAV:calendar-multiget>
+
+ # <CalDAV:calendar-multiget> element
+ multiget = Element(caldavxml.calendar_multiget)
+
+ if self.props:
+ # <DAV:prop> element
+ prop = SubElement(multiget, davxml.prop)
+
+ # Now add each property
+ for propname in self.props:
+ # Add property element taking namespace into account
+ SubElement(prop, propname)
+
+ # Now add each href
+ for href in self.hrefs:
+ # Add href elements
+ SubElement(multiget, davxml.href).text = href
+
+ # Now we have the complete document, so write it out (no indentation)
+ xmldoc = BetterElementTree(multiget)
+ xmldoc.writeUTF8(os)
Added: CalDAVClientLibrary/trunk/src/protocol/caldav/tests/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/caldav/tests/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/caldav/tests/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,16 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
Added: CalDAVClientLibrary/trunk/src/protocol/caldav/tests/test_makecalendar.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/caldav/tests/test_makecalendar.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/caldav/tests/test_makecalendar.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,111 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from protocol.webdav.session import Session
+from protocol.caldav.makecalendar import MakeCalendar
+from StringIO import StringIO
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = MakeCalendar(server, "/")
+ self.assertEqual(request.getMethod(), "MKCALENDAR")
+
+class TestRequestHeaders(unittest.TestCase):
+ pass
+
+class TestRequestBody(unittest.TestCase):
+
+ def test_GenerateXMLDisplayname(self):
+
+ server = Session("www.example.com")
+ request = MakeCalendar(server, "/", "home")
+ os = StringIO()
+ request.generateXML(os)
+ self.assertEqual(os.getvalue(), """<?xml version='1.0' encoding='utf-8'?>
+<ns0:mkcalendar xmlns:ns0="urn:ietf:params:xml:ns:caldav">
+ <ns1:prop xmlns:ns1="DAV:">
+ <ns1:displayname>home</ns1:displayname>
+ </ns1:prop>
+</ns0:mkcalendar>
+""".replace("\n", "\r\n")
+)
+
+ def test_GenerateXMLMultipleProperties(self):
+
+ server = Session("www.example.com")
+ request = MakeCalendar(server, "/", "home", "my personal calendar")
+ os = StringIO()
+ request.generateXML(os)
+ self.assertEqual(os.getvalue(), """<?xml version='1.0' encoding='utf-8'?>
+<ns0:mkcalendar xmlns:ns0="urn:ietf:params:xml:ns:caldav">
+ <ns1:prop xmlns:ns1="DAV:">
+ <ns1:displayname>home</ns1:displayname>
+ <ns0:calendar-description>my personal calendar</ns0:calendar-description>
+ </ns1:prop>
+</ns0:mkcalendar>
+""".replace("\n", "\r\n")
+)
+
+ def test_GenerateXMLCDATAProperty(self):
+
+ server = Session("www.example.com")
+ timezone = """BEGIN:VCALENDAR
+PRODID:-//Example Corp.//CalDAV Client//EN
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:US-Eastern
+LAST-MODIFIED:19870101T000000Z
+BEGIN:STANDARD
+DTSTART:19671029T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+TZNAME:Eastern Standard Time (US & Canada)
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19870405T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+TZNAME:Eastern Daylight Time (US & Canada)
+END:DAYLIGHT
+END:VTIMEZONE
+END:VCALENDAR
+""".replace("\n", "\r\n")
+ request = MakeCalendar(server, "/", timezone=timezone)
+ os = StringIO()
+ request.generateXML(os)
+ self.assertEqual(os.getvalue(), """<?xml version='1.0' encoding='utf-8'?>
+<ns0:mkcalendar xmlns:ns0="urn:ietf:params:xml:ns:caldav">
+ <ns1:prop xmlns:ns1="DAV:">
+ <ns0:calendar-timezone>%s</ns0:calendar-timezone>
+ </ns1:prop>
+</ns0:mkcalendar>
+""".replace("\n", "\r\n") % (timezone.replace("&", "&"),)
+)
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+ pass
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/caldav/tests/test_multiget.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/caldav/tests/test_multiget.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/caldav/tests/test_multiget.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,105 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from protocol.webdav.session import Session
+from protocol.caldav.multiget import Multiget
+from StringIO import StringIO
+from protocol.webdav.definitions import davxml
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = Multiget(server, "/", ())
+ self.assertEqual(request.getMethod(), "REPORT")
+
+class TestRequestHeaders(unittest.TestCase):
+ pass
+
+class TestRequestBody(unittest.TestCase):
+
+ def test_GenerateXMLOneHrefOnly(self):
+
+ server = Session("www.example.com")
+ request = Multiget(server, "/", ("/a",))
+ os = StringIO()
+ request.generateXML(os)
+ self.assertEqual(os.getvalue(), """<?xml version='1.0' encoding='utf-8'?>
+<ns0:calendar-multiget xmlns:ns0="urn:ietf:params:xml:ns:caldav">
+ <ns1:href xmlns:ns1="DAV:">/a</ns1:href>
+</ns0:calendar-multiget>
+""".replace("\n", "\r\n")
+)
+
+ def test_GenerateXMLMultipleHrefsOnly(self):
+
+ server = Session("www.example.com")
+ request = Multiget(server, "/", ("/a", "/b",))
+ os = StringIO()
+ request.generateXML(os)
+ self.assertEqual(os.getvalue(), """<?xml version='1.0' encoding='utf-8'?>
+<ns0:calendar-multiget xmlns:ns0="urn:ietf:params:xml:ns:caldav">
+ <ns1:href xmlns:ns1="DAV:">/a</ns1:href>
+ <ns1:href xmlns:ns1="DAV:">/b</ns1:href>
+</ns0:calendar-multiget>
+""".replace("\n", "\r\n")
+)
+
+ def test_GenerateXMLMultipleHrefsOneProperty(self):
+
+ server = Session("www.example.com")
+ request = Multiget(server, "/", ("/a", "/b",), (davxml.getetag,))
+ os = StringIO()
+ request.generateXML(os)
+ self.assertEqual(os.getvalue(), """<?xml version='1.0' encoding='utf-8'?>
+<ns0:calendar-multiget xmlns:ns0="urn:ietf:params:xml:ns:caldav">
+ <ns1:prop xmlns:ns1="DAV:">
+ <ns1:getetag />
+ </ns1:prop>
+ <ns1:href xmlns:ns1="DAV:">/a</ns1:href>
+ <ns1:href xmlns:ns1="DAV:">/b</ns1:href>
+</ns0:calendar-multiget>
+""".replace("\n", "\r\n")
+)
+
+ def test_GenerateXMLMultipleHrefsMultipleProperties(self):
+
+ server = Session("www.example.com")
+ request = Multiget(server, "/", ("/a", "/b",), (davxml.getetag, davxml.displayname,))
+ os = StringIO()
+ request.generateXML(os)
+ self.assertEqual(os.getvalue(), """<?xml version='1.0' encoding='utf-8'?>
+<ns0:calendar-multiget xmlns:ns0="urn:ietf:params:xml:ns:caldav">
+ <ns1:prop xmlns:ns1="DAV:">
+ <ns1:getetag />
+ <ns1:displayname />
+ </ns1:prop>
+ <ns1:href xmlns:ns1="DAV:">/a</ns1:href>
+ <ns1:href xmlns:ns1="DAV:">/b</ns1:href>
+</ns0:calendar-multiget>
+""".replace("\n", "\r\n")
+)
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+ pass
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/http/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,16 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
Added: CalDAVClientLibrary/trunk/src/protocol/http/authentication/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/authentication/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/authentication/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,16 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
Added: CalDAVClientLibrary/trunk/src/protocol/http/authentication/authenticator.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/authentication/authenticator.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/authentication/authenticator.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,20 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+class Authenticator(object):
+
+ def addHeaders(self, hdrs, request):
+ raise NotImplementedError
Added: CalDAVClientLibrary/trunk/src/protocol/http/authentication/basic.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/authentication/basic.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/authentication/basic.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,36 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from protocol.http.authentication.authenticator import Authenticator
+from protocol.http.definitions import headers
+
+class Basic(Authenticator):
+
+ def __init__(self, user, pswd):
+ self.user = user
+ self.pswd = pswd
+
+ def setDetails(self, user, pswd):
+ self.user = user
+ self.pswd = pswd
+
+ def addHeaders(self, hdrs, request):
+ # Generate the base64 encoded string
+ encode = self.user + ":" + self.pswd
+ base64 = encode.encode("base64").strip()
+
+ # Generate header
+ hdrs.append((headers.Authorization, "Basic %s" % (base64,)))
Added: CalDAVClientLibrary/trunk/src/protocol/http/authentication/digest.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/authentication/digest.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/authentication/digest.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,218 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from protocol.http.authentication.authenticator import Authenticator
+from protocol.http.util import parsetoken
+from protocol.http.definitions import headers
+from StringIO import StringIO
+import sha
+import md5
+
+class Digest(Authenticator):
+
+ def __init__(self, user, pswd, www_authenticate):
+ self.fields = {}
+ self.fields['username'] = user
+ self.fields['password'] = pswd
+ self.parseAuthenticateHeader(www_authenticate)
+ self.stale = False
+ self.clientCount = 0
+
+ def setDetails(self, user, pswd, www_authenticate):
+ self.fields['username'] = user
+ self.fields['password'] = pswd
+ self.parseAuthenticateHeader(www_authenticate)
+
+ def addHeaders(self, hdrs, request):
+ # Generate response
+ self.generateResponse(request)
+
+ # Generate header
+ os = StringIO()
+ os.write("Digest username=\"%s\"," % (self.fields['username'],))
+ os.write(" realm=\"%s\"," % (self.fields['realm'],))
+ os.write(" nonce=\"%s\"," % (self.fields['nonce'],))
+ os.write(" uri=\"%s\"," % (request.getURL(),))
+ if "qop" in self.fields:
+ os.write(" qop=auth,")
+ os.write(" nc=\"%s\"" % (self.fields['nc'],))
+ os.write(" cnonce=\"%s\"" % (self.fields['cnonce'],))
+ os.write(" response=\"%s\"" % (self.response,))
+
+ if "algorithm" in self.fields:
+ os.write(", algorithm=\"%s\"" % (self.fields['algorithm'],))
+ if "opaque" in self.fields:
+ os.write(", opaque=\"%s\"" % (self.fields['opaque'],))
+
+ hdrs.append((headers.Authorization, os.getvalue()))
+
+ def parseAuthenticateHeader(self, hdrs):
+ for hdr in hdrs:
+
+ # Strip any space
+ hdr = hdr.strip()
+
+ # Must have Digest token
+ if hdr[:7].lower() != "digest ":
+ continue
+ else:
+ hdr = hdr[7:]
+
+ # Get each name/value pair
+ while True:
+ name, hdr = parsetoken(hdr, " \t=")
+
+ if not name or not hdr:
+ return
+ name = name.lower()
+
+ value, hdr = parsetoken(hdr, ", ")
+ if not value:
+ return
+
+ if name in ("realm", "domain", "nonce", "opaque", "algorithm", "qop"):
+ self.fields[name] = value
+
+ elif name == "stale":
+ self.stale = (value.lower() != "false")
+
+ else:
+ # Unknown token - ignore
+ pass
+
+ # Punt over space
+ hdr = hdr.strip()
+
+ break
+
+ algorithms = {
+ 'md5': md5.new,
+ 'md5-sess': md5.new,
+ 'sha': sha.new,
+ }
+
+ # DigestCalcHA1
+ @staticmethod
+ 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 = Digest.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 = Digest.algorithms[pszAlg]()
+ m.update(HA1)
+ m.update(":")
+ m.update(pszNonce)
+ m.update(":")
+ m.update(pszCNonce)
+ HA1 = m.digest()
+
+ return HA1.encode('hex')
+
+ # DigestCalcResponse
+ @staticmethod
+ def calcResponse(
+ HA1,
+ algo,
+ pszNonce,
+ pszNonceCount,
+ pszCNonce,
+ pszQop,
+ pszMethod,
+ pszDigestUri,
+ pszHEntity,
+ ):
+ m = Digest.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 = Digest.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
+
+ def generateResponse(self, request):
+ self.response = Digest.calcResponse(
+ Digest.calcHA1(
+ self.fields.get("algorithm", "md5"),
+ self.fields.get("username", ""),
+ self.fields.get("realm", ""),
+ self.fields.get("password", ""),
+ self.fields.get("nonce", ""),
+ self.fields.get("cnonce", ""),
+ ),
+ self.fields.get("algorithm", "md5"),
+ self.fields.get("nonce", ""),
+ self.fields.get("nc", ""),
+ self.fields.get("cnonce", ""),
+ self.fields.get("qop", ""),
+ request.method,
+ request.url,
+ None,
+ )
+
Added: CalDAVClientLibrary/trunk/src/protocol/http/authentication/tests/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/authentication/tests/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/authentication/tests/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,16 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
Added: CalDAVClientLibrary/trunk/src/protocol/http/authentication/tests/test_basic.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/authentication/tests/test_basic.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/authentication/tests/test_basic.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,29 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from protocol.http.authentication.basic import Basic
+from protocol.http.definitions import headers
+
+import unittest
+
+class TestBasic(unittest.TestCase):
+
+ def testBasic(self):
+
+ auther = Basic("user", "pswd")
+ hdrs = []
+ auther.addHeaders(hdrs, None)
+ self.assertTrue((headers.Authorization, "Basic dXNlcjpwc3dk") in hdrs)
Added: CalDAVClientLibrary/trunk/src/protocol/http/data/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/data/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/data/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,16 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
Added: CalDAVClientLibrary/trunk/src/protocol/http/data/data.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/data/data.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/data/data.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,46 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+
+class Data(object):
+
+ def __init__(self):
+ pass
+
+ def start(self):
+ pass
+
+ def stop(self):
+ pass
+
+class RequestData(Data):
+
+ def getContentLength(self):
+ return self.content_length
+
+ def getContentType(self):
+ return self.content_type
+
+ def read(self):
+ raise NotImplementedError
+
+class ResponseData(Data):
+
+ def write(self, data):
+ raise NotImplementedError
+
+ def clear(self):
+ raise NotImplementedError
Added: CalDAVClientLibrary/trunk/src/protocol/http/data/file.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/data/file.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/data/file.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,68 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from protocol.http.data.data import ResponseData
+from protocol.http.data.data import RequestData
+import stat
+import os
+
+class RequestDataFile(RequestData):
+
+ def __init__(self, fname, content_type):
+
+ # Cache file name
+ self.fname = fname
+
+ # Determine size of stream
+ self.content_length = os.stat(self.fname)[stat.ST_SIZE]
+
+ self.content_type = content_type
+
+ def start(self):
+ # Create an input file stream
+ self.stream = open(self.fname, "r")
+
+ def stop(self):
+ self.stream.close()
+ self.stream = None
+
+ def read(self):
+ data = self.stream.read(8192)
+ if data:
+ return data, True
+ else:
+ return data, False
+
+class ResponseDataFile(ResponseData):
+
+ def __init__(self, fname):
+ self.fname = fname
+
+ def start(self):
+ # Create an input file stream
+ self.stream = open(self.fname, "w")
+
+ def stop(self):
+ self.stream.close()
+ self.stream = None
+
+ def write(self, data):
+ self.stream.write(data)
+
+ def clear(self):
+ # Throw out existing data and start from scratch
+ self.stop()
+ self.start()
Added: CalDAVClientLibrary/trunk/src/protocol/http/data/string.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/data/string.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/data/string.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,49 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from protocol.http.data.data import ResponseData
+from protocol.http.data.data import RequestData
+from StringIO import StringIO
+
+class RequestDataString(RequestData):
+
+ def __init__(self, text, content_type):
+
+ # Cache file name
+ self.text = text
+
+ # Determine size of stream
+ self.content_length = len(text)
+
+ self.content_type = content_type
+
+ def read(self):
+ return self.text, False
+
+class ResponseDataString(ResponseData):
+
+ def __init__(self):
+ self.stream = StringIO()
+
+ def getData(self):
+ return self.stream.getvalue()
+
+ def write(self, data):
+ self.stream.write(data)
+
+ def clear(self):
+ # Throw out existing data and start from scratch
+ self.stream = StringIO()
Added: CalDAVClientLibrary/trunk/src/protocol/http/definitions/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/definitions/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/definitions/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,16 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
Added: CalDAVClientLibrary/trunk/src/protocol/http/definitions/headers.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/definitions/headers.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/definitions/headers.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,45 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+# HTTP protocol headers
+
+# RFC2616 4.5 - General Header fields (only the ones we need)
+
+Connection = "Connection"
+ConnectionClose = "close"
+Date = "Date"
+TransferEncoding = "Transfer-Encoding"
+TransferEncodingChunked = "chunked"
+
+# RFC2616 5.3 - Request Header fields (only the ones we need)
+
+Authorization = "Authorization"
+Host = "Host"
+IfMatch = "If-Match"
+IfNoneMatch = "If-None-Match"
+
+# RFC2616 6.2 - Response Header fields (only the ones we need)
+
+ETag = "ETag"
+Location = "Location"
+Server = "Server"
+WWWAuthenticate = "WWW-Authenticate"
+
+# RFC2616 7.1 - Entity Header fields (only the ones we need)
+
+Allow = "Allow"
+ContentLength = "Content-Length"
+ContentType = "Content-Type"
Added: CalDAVClientLibrary/trunk/src/protocol/http/definitions/methods.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/definitions/methods.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/definitions/methods.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,27 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+
+# RFC2616 5.1.1 - Request Methods
+
+OPTIONS = "OPTIONS"
+GET = "GET"
+HEAD = "HEAD"
+POST = "POST"
+PUT = "PUT"
+DELETE = "DELETE"
+TRACE = "TRACE"
+CONNECT = "CONNECT"
Added: CalDAVClientLibrary/trunk/src/protocol/http/definitions/statuscodes.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/definitions/statuscodes.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/definitions/statuscodes.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,58 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+
+Unknown = 0
+Continue = 100
+SwitchingProtocols = 101
+OK = 200
+Created = 201
+Accepted = 202
+NonAuthoritativeInformation = 203
+NoContent = 204
+ResetContent = 205
+PartialContent = 206
+MultipleChoices = 300
+MovedPermanently = 301
+Found = 302
+SeeOther = 303
+NotModified = 304
+UseProxy = 305
+TemporaryRedirect = 307
+BadRequest = 400
+Unauthorized = 401
+PaymentRequired = 402
+Forbidden = 403
+NotFound = 404
+MethodNotAllowed = 405
+NotAcceptable = 406
+ProxyAuthenticationRequired = 407
+RequestTimeout = 408
+Conflict = 409
+Gone = 410
+LengthRequired = 411
+PreconditionFailed = 412
+RequestEntityTooLarge = 413
+RequestURITooLarge = 414
+UnsupportedMediaType = 415
+RequestedRangeNotSatisfiable = 416
+ExpectationFailed = 417
+InternalServerError = 500
+NotImplemented = 501
+BadGateway = 502
+ServiceUnavailable = 503
+GatewayTimeout = 504
+HTTPVersionNotSupported = 505
Added: CalDAVClientLibrary/trunk/src/protocol/http/requestresponse.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/requestresponse.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/requestresponse.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,274 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from StringIO import StringIO
+from protocol.http.definitions import headers
+from protocol.http.definitions import methods
+from protocol.http.definitions import statuscodes
+
+class ResponseError(Exception):
+ pass
+
+class RequestResponse(object):
+
+ def __init__(self, session, method, url, etag=None, etag_match=False):
+ self._initResponse()
+ self.session = session
+ self.method = method
+ self.url = url
+ self.etag = etag
+ self.etag_match = etag_match
+
+ def _initResponse(self):
+ self.session = None
+ self.request_data = None
+ self.response_data = None
+ self.method = methods.GET
+ self.url = None
+ self.etag = None
+ self.etag_match = False
+ self.status_code = statuscodes.Unknown
+ self.status_reason = None
+ self.headers = {}
+ self.connection_close = False
+ self.content_length = 0
+ self.chunked = False
+ self.completed = False
+
+ def setSession(self, session):
+ self.session = session
+ def getSession(self):
+ return self.session
+
+ def getMethod(self):
+ return self.method
+
+ def setURL(self, ruri):
+ self.url = ruri
+ def getURL(self):
+ return self.url
+
+ def setETag(self, etag, etag_match):
+ self.etag = etag
+ self.etag_match = etag_match
+
+ def queuedForSending(self, session):
+ self.session = session
+
+ def setData(self, request_data, response_data):
+ self.request_data = request_data
+ self.response_data = response_data
+
+ def hasRequestData(self):
+ return self.request_data != None
+
+ def hasResponseData(self):
+ return self.response_data != None
+
+ def getRequestData(self):
+ if self.request_data:
+ return self.request_data
+ else:
+ return None
+
+ def getResponseData(self):
+ if self.response_data:
+ return self.response_data
+ else:
+ return None
+
+ def getRequestStartLine(self):
+ return "%s %s HTTP/1.1" % (self.method, self.url,)
+
+ def getRequestHeaders(self):
+ # This will be overridden by sub-classes that add headers - those classes should
+ # call this class's implementation to write out the basic set of headers
+ result = []
+ self.addHeaders(result)
+ return tuple(result)
+
+ def generateRequestHeader(self):
+ os = StringIO()
+ os.write("%s\r\n" % (self.getRequestStartLine(),))
+ for header, value in self.getRequestHeaders():
+ os.write("%s: %s\r\n" % (header, value,))
+ os.write("\r\n")
+ return os.getvalue()
+
+ def addHeaders(self, hdrs):
+
+ # Write host
+ hdrs.append((headers.Host, self.session.server))
+
+ # Do ETag matching
+ if self.etag:
+ if self.etag_match:
+ hdrs.append((headers.IfMatch, self.etag))
+ else:
+ hdrs.append((headers.IfNoneMatch, self.etag))
+
+ # Do session global headers
+ self.session.addHeaders(hdrs, self)
+
+ # Check for content
+ self.addContentHeaders(hdrs)
+
+ def addContentHeaders(self, hdrs):
+ # Check for content
+ if self.hasRequestData():
+ hdrs.append((headers.ContentLength, self.request_data.getContentLength()))
+ hdrs.append((headers.ContentType, self.request_data.getContentType()))
+
+ def setResponseStatus(self, version, status, reason):
+ self.status_code = status
+ self.status_reason = reason
+
+ def setResponseHeaders(self, hdrs):
+ for header in hdrs:
+ splits = header.split(":")
+ self.headers.setdefault(splits[0].strip().lower(), []).append(splits[1].strip())
+
+ # Now cache some useful header values
+ self.cacheHeaders()
+
+ def clearResponse(self):
+ self.etag_match = False
+ self.status_code = statuscodes.Unknown
+ self.status_reason = None
+ self.headers = {}
+ self.connection_close = False
+ self.content_length = 0
+ self.chunked = False
+ self.completed = False
+
+ if self.response_data:
+ self.response_data.clear()
+
+ def getStatusCode(self):
+ return self.status_code
+ def getStatusReason(self):
+ return self.status_reason
+
+ def getConnectionClose(self):
+ return self.connection_close
+
+ def getContentLength(self):
+ return self.content_length
+ def getChunked(self):
+ return self.chunked
+
+ def setComplete(self):
+ self.completed = True
+ def getCompleted(self):
+ return self.completed
+
+ def hasResponseHeader(self, hdr):
+ return self.headers.has_key(hdr.lower())
+
+ def getResponseHeader(self, hdr):
+ if self.headers.has_key(hdr.lower()):
+ return self.headers[hdr.lower()][0]
+ else:
+ return ""
+
+ def getResponseHeaders(self, hdr=None):
+ if hdr:
+ if self.headers.has_key(hdr.lower()):
+ return self.headers[hdr.lower()]
+ else:
+ return ()
+ else:
+ return self.headers
+
+ def isRedirect(self):
+ # Only these are allowed
+ return self.status_code in (statuscodes.MovedPermanently, statuscodes.Found, statuscodes.TemporaryRedirect)
+
+ def parseStatusLine(self, line):
+
+ # Must have 'HTTP/' version at start
+ if line[0:5] != "HTTP/":
+ raise ResponseError("status line incorrect at start")
+
+ # Must have version '1.1 '
+ if line[5:9] != "1.1 ":
+ raise ResponseError("incorrect http version in status line")
+
+ # Must have three digits followed by nothing or one space
+ if not line[9:12].isdigit() or (len(line) > 12 and line[12] != " "):
+ raise ResponseError("invalid status response code syntax")
+
+ # Read in the status code
+ self.status_code = int(line[9:12])
+
+ # Remainder is reason
+ if len(line) > 13:
+ self.status_reason = line[13:]
+
+ def readFoldedLine(self, instream, line1, line2, log):
+ # If line2 already has data, transfer that into line1
+ if line2 or line1:
+ line1 = line2
+ else:
+ # Fill first line
+ line1 = instream.readline()
+ if not line1:
+ return False, line1, line2
+ line1 = line1.rstrip("\r\n")
+
+ if log:
+ log.write("%s\n" % (line1,))
+
+ # Terminate on blank line which is end of headers
+ if not line1:
+ return True, line1, line2
+
+ # Now loop looking ahead at the next line to see if it is folded
+ while True:
+ # Get next line
+ line2 = instream.readline()
+ if not line2:
+ return True, line1, line2
+ line2 = line2.rstrip("\r\n")
+
+ if log:
+ log.write("%s\n" % (line2,))
+
+ # Does it start with a space => folded
+ if line2 and line2[0].isspace():
+ # Copy folded line (without space) to current line and cycle for more
+ line1 += line2[1:]
+ else:
+ # Not folded - just exit loop
+ break
+
+ return True, line1, line2
+
+ def cacheHeaders(self):
+ # Connection
+ if self.headers.has_key(headers.Connection):
+ value = self.headers[headers.Connection][0]
+ self.connection_close = (value.lower() == headers.ConnectionClose)
+
+ # Content-Length
+ if self.headers.has_key(headers.ContentLength):
+ value = self.headers[headers.ContentLength][0]
+ self.content_length = int(value)
+
+ # Transfer encoding
+ if self.headers.has_key(headers.TransferEncoding):
+ value = self.headers[headers.TransferEncoding][0]
+ self.chunked = (value == headers.TransferEncodingChunked)
Added: CalDAVClientLibrary/trunk/src/protocol/http/session.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/session.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/session.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,122 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from httplib import InvalidURL
+import httplib
+
+class Session(object):
+
+ STATE_OPEN = 0
+ STATE_CLOSED = 1
+
+ def __init__(self, server, port=None, ssl=False, log=None):
+
+ self.server = server
+ self.port = port
+ self.ssl = ssl
+ if not self.port:
+ self.port = httplib.HTTPS_PORT if ssl else httplib.HTTP_PORT
+ self.authorization = None
+ self.connection_state = Session.STATE_CLOSED
+ self.log = log
+
+ def hasAuthorization(self):
+ return self.authorization != None
+
+ def getAuthorization(self):
+ return self.authorization
+
+ def addHeaders(self, hdrs, request):
+ if self.hasAuthorization():
+ self.getAuthorization().addHeaders(hdrs, request)
+
+ def setServer(self, server, port=None):
+
+ if port is None:
+ i = server.rfind(':')
+ j = server.rfind(']')
+ if i > j:
+ try:
+ port = int(server[i+1:])
+ except ValueError:
+ raise InvalidURL("nonnumeric port: '%s'" % server[i+1:])
+ server = server[:i]
+ else:
+ port = httplib.HTTPS_PORT if self.ssl else httplib.HTTP_PORT
+ if server and server[0] == '[' and server[-1] == ']':
+ server = server[1:-1]
+
+ if self.server != server:
+ self.server = server
+ self.port = port
+
+ # Always clear out authorization when host changes
+ self.authorization = None
+
+ def sendRequest(self, request):
+ try:
+
+ # First need a connection
+ self.needConnection();
+
+ # Now do the connection
+ self.doRequest(request);
+
+ # Check the final connection state and close if that's what the server wants
+ if request.getConnectionClose():
+ self.closeConnection()
+
+ except Exception:
+
+ # log.err(e)
+ self.connection_state = Session.STATE_CLOSED
+ raise
+
+ def handleHTTPError(self, request):
+ raise NotImplementedError
+
+ def displayHTTPError(self, request):
+ raise NotImplementedError
+
+ def isConnectionOpen(self):
+ return self.connection_state == Session.STATE_OPEN
+
+ def needConnection(self):
+ if not self.isConnectionOpen():
+ self.openConnection()
+
+ def openConnection(self):
+ if not self.isConnectionOpen():
+ self.openSession()
+ self.connection_state = Session.STATE_OPEN
+
+ def closeConnection(self):
+ if self.isConnectionOpen():
+ self.closeSession();
+ self.connection_state = Session.STATE_CLOSED
+
+ def openSession(self):
+ raise NotImplementedError
+
+ def closeSession(self):
+ raise NotImplementedError
+
+ def runSession(self, request):
+ raise NotImplementedError
+
+ def doRequest(self, request):
+ raise NotImplementedError
+
\ No newline at end of file
Added: CalDAVClientLibrary/trunk/src/protocol/http/tests/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/tests/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/tests/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,16 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
Added: CalDAVClientLibrary/trunk/src/protocol/http/tests/test_requestresponse.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/tests/test_requestresponse.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/tests/test_requestresponse.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,89 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from protocol.http.requestresponse import RequestResponse
+from protocol.http.session import Session
+from protocol.http.data.string import RequestDataString
+from protocol.http.authentication.basic import Basic
+from protocol.http.definitions import methods
+
+import unittest
+
+class TestRequestHeaders(unittest.TestCase):
+
+ def test_NoEtag(self):
+
+ server = Session("www.example.com")
+ request = RequestResponse(server, methods.GET, "/")
+ self.assertEqual(request.generateRequestHeader(), """GET / HTTP/1.1
+Host: www.example.com
+
+""".replace("\n", "\r\n")
+)
+
+ def test_EtagMatch(self):
+
+ server = Session("www.example.com")
+ request = RequestResponse(server, methods.GET, "/", "\"etag\"", True)
+ self.assertEqual(request.generateRequestHeader(), """GET / HTTP/1.1
+Host: www.example.com
+If-Match: "etag"
+
+""".replace("\n", "\r\n")
+)
+
+ def test_EtagNoneMatch(self):
+
+ server = Session("www.example.com")
+ request = RequestResponse(server, methods.GET, "/", "\"etag\"", False)
+ self.assertEqual(request.generateRequestHeader(), """GET / HTTP/1.1
+Host: www.example.com
+If-None-Match: "etag"
+
+""".replace("\n", "\r\n")
+)
+
+ def test_Content(self):
+
+ server = Session("www.example.com")
+ request = RequestResponse(server, methods.GET, "/")
+ rawdata = "Here is some data\r\non multiple lines."
+ data = RequestDataString(rawdata, "text/plain")
+ request.setData(data, None)
+ self.assertEqual(request.generateRequestHeader(), """GET / HTTP/1.1
+Host: www.example.com
+Content-Length: %d
+Content-Type: text/plain
+
+""".replace("\n", "\r\n") % (len(rawdata),)
+)
+
+ def test_ContentAndAuthorization(self):
+
+ server = Session("www.example.com")
+ server.authorization = Basic("user", "pswd")
+ request = RequestResponse(server, methods.GET, "/")
+ rawdata = "Here is some data\r\non multiple lines."
+ data = RequestDataString(rawdata, "text/plain")
+ request.setData(data, None)
+ self.assertEqual(request.generateRequestHeader(), """GET / HTTP/1.1
+Host: www.example.com
+Authorization: Basic dXNlcjpwc3dk
+Content-Length: %d
+Content-Type: text/plain
+
+""".replace("\n", "\r\n") % (len(rawdata),)
+)
Added: CalDAVClientLibrary/trunk/src/protocol/http/tests/test_util.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/tests/test_util.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/tests/test_util.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,107 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from protocol.http.util import parsequoted
+from protocol.http.util import parsetoken
+from protocol.http.util import parseStatusLine
+
+import unittest
+
+class TestParseQuoted(unittest.TestCase):
+
+ def testParseQuotedOK(self):
+
+ data = {
+ "\"\"" : ("", ""),
+ "\"quoted\"" : ("quoted", ""),
+ "\"quoted words\"" : ("quoted words", ""),
+ "\"quoting a \\\"word\\\"\"" : ("quoting a \"word\"", ""),
+ "\"\" after" : ("", "after"),
+ "\"quoted\" after" : ("quoted", "after"),
+ "\"quoted words\" after" : ("quoted words", "after"),
+ "\"quoting a \\\"word\\\"\" after" : ("quoting a \"word\"", "after"),
+ "\"quoting a \\\"word\\\" after\" after": ("quoting a \"word\" after", "after"),
+ "\"quoted\"after" : ("quoted", "after"),
+ "\"" : ("", ""),
+ "\"unterminated" : ("unterminated", ""),
+ "\"unterminated words" : ("unterminated words", ""),
+ "\"unterminated a \\\"word\\\"" : ("unterminated a \"word\"", ""),
+ }
+
+ for input, result in data.iteritems():
+ self.assertEqual(parsequoted(input), result)
+
+ def testParseQuotedBAD(self):
+
+ data = (
+ "",
+ "unquoted",
+ "unquoted \"quoted\"",
+ )
+
+ for input in data:
+ self.assertRaises(AssertionError, parsequoted, input)
+
+class TestParseToken(unittest.TestCase):
+
+ def testParseTokenOK(self):
+
+ data = {
+ "" : ("", ""),
+ "unquoted" : ("unquoted", ""),
+ "unquoted words" : ("unquoted", "words"),
+ "unquoted words" : ("unquoted", "words"),
+ "unquoting a \"word\"" : ("unquoting", "a \"word\""),
+ "unquoted\twords" : ("unquoted", "words"),
+ "unquoting\ta \"word\"" : ("unquoting", "a \"word\""),
+ "unquoted: words" : ("unquoted", "words"),
+ "unquoting: a \"word\"" : ("unquoting", "a \"word\""),
+
+ "\"\"" : ("", ""),
+ "\"quoted\"" : ("quoted", ""),
+ "\"quoted words\"" : ("quoted words", ""),
+ "\"quoting a \\\"word\\\"\"" : ("quoting a \"word\"", ""),
+ "\"\" after" : ("", "after"),
+ "\"quoted\" after" : ("quoted", "after"),
+ "\"quoted words\" after" : ("quoted words", "after"),
+ "\"quoting a \\\"word\\\"\" after" : ("quoting a \"word\"", "after"),
+ "\"quoting a \\\"word\\\" after\" after": ("quoting a \"word\" after", "after"),
+ "\"quoted\"after" : ("quoted", "after"),
+ "\"" : ("", ""),
+ "\"unterminated" : ("unterminated", ""),
+ "\"unterminated words" : ("unterminated words", ""),
+ "\"unterminated a \\\"word\\\"" : ("unterminated a \"word\"", ""),
+ }
+
+ for input, result in data.iteritems():
+ self.assertEqual(parsetoken(input, " \t:"), result)
+
+class TestParseStatusLine(unittest.TestCase):
+
+ def testParseTokenOK(self):
+ self.assertEqual(parseStatusLine("HTTP/1.1 200 OK"), 200)
+
+ def testParseTokenBadStatus(self):
+ self.assertEqual(parseStatusLine("HTTP/1.2 2001 OK"), 0)
+
+ def testParseTokenBadVersion(self):
+ self.assertEqual(parseStatusLine("HTTP/1.2 200 OK"), 0)
+
+ def testParseTokenBadNumber(self):
+ self.assertEqual(parseStatusLine("HTTP/1.1 OK"), 0)
+
+ def testParseTokenBad(self):
+ self.assertEqual(parseStatusLine("HTTP/1.1"), 0)
Added: CalDAVClientLibrary/trunk/src/protocol/http/util.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/http/util.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/http/util.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,74 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+
+def parsetoken(text, delimiters=" \t"):
+
+ if not text:
+ return "", ""
+
+ if text[0] == '"':
+ return parsequoted(text, delimiters)
+ else:
+ for pos, c in enumerate(text):
+ if c in delimiters:
+ token = text[0:pos]
+ break
+ else:
+ return text, ""
+
+ return token, lstripdelimiters(text[pos:], delimiters)
+
+def parsequoted(text, delimiters=" \t"):
+
+ assert(text)
+ assert(text[0] == '"')
+
+ pos = 1
+ while True:
+ next_pos = text.find('"', pos)
+ if next_pos == -1:
+ return text[1:].replace("\\\\", "\\").replace("\\\"", "\""), ""
+ if text[next_pos - 1] == '\\':
+ pos = next_pos + 1
+ else:
+ return (
+ text[1:next_pos].replace("\\\\", "\\").replace("\\\"", "\""),
+ lstripdelimiters(text[next_pos+1:], delimiters)
+ )
+
+def lstripdelimiters(text, delimiters):
+ for pos, c in enumerate(text):
+ if c not in delimiters:
+ return text[pos:]
+ else:
+ return ""
+
+def parseStatusLine(status):
+
+ status = status.strip()
+
+ # Must have 'HTTP/1.1' version at start
+ if status[0:9] != "HTTP/1.1 ":
+ return 0
+
+ # Must have three digits followed by nothing or one space
+ if not status[9:12].isdigit() or (len(status) > 12 and status[12] != " "):
+ return 0
+
+ # Read in the status code
+ return int(status[9:12])
+
Added: CalDAVClientLibrary/trunk/src/protocol/tests/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/tests/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/tests/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2007-2008 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.
+##
Added: CalDAVClientLibrary/trunk/src/protocol/tests/test_url.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/tests/test_url.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/tests/test_url.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,53 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.url import URL
+
+import unittest
+
+class TestURLParse(unittest.TestCase):
+
+ def verifyParts(self, u, s, scheme, server, path, extended):
+ self.assertEqual(u.toString(), s)
+ self.assertEqual(u.scheme, scheme)
+ self.assertEqual(u.server, server)
+ self.assertEqual(u.path, path)
+ self.assertEqual(u.extended, extended)
+
+ def test_ParsePlain(self):
+
+ s = "http://www.example.com"
+ u = URL(url=s)
+ self.verifyParts(u, s, "http", "www.example.com", "", "")
+
+ def test_ParsePlainPath(self):
+
+ s = "http://www.example.com/principals/users"
+ u = URL(url=s)
+ self.verifyParts(u, s, "http", "www.example.com", "/principals/users", "")
+
+ def test_ParsePlainPathExtended(self):
+
+ s = "http://www.example.com/principals/users?test=true"
+ u = URL(url=s)
+ self.verifyParts(u, s, "http", "www.example.com", "/principals/users", "?test=true")
+
+ def test_ParseMailto(self):
+
+ s = "mailto:user at example.com"
+ u = URL(url=s)
+ self.verifyParts(u, s, "mailto", "user at example.com", "", "")
+
Added: CalDAVClientLibrary/trunk/src/protocol/url.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/url.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/url.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,219 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+import os
+import urllib
+
+class URL(object):
+
+ eAbsolute = 0
+ eRelative = 1
+ eLastPath = 2
+
+ URLEscape = '%'
+ URLReserved = "/?:@&="
+ URLUnreserved = ( # Allowable URL chars
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 0 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 16 - 31
+ 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, # 32 - 47
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, # 48 - 63
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 64 - 79
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, # 80 - 95
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 96 - 111
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, # 112 - 127
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 128 - 143
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 144 - 159
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 160 - 175
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 176 - 191
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 192 - 207
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 208 - 223
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 224 - 239
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 240 - 255
+ )
+
+ URLCharacter = ( # Allowable URL chars -- all
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 0 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 16 - 31
+ 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 32 - 47
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, # 48 - 63
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 64 - 79
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, # 80 - 95
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 96 - 111
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, # 112 - 127
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 128 - 143
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 144 - 159
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 160 - 175
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 176 - 191
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 192 - 207
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 208 - 223
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 224 - 239
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 240 - 255
+ )
+
+ URLXCharacter = ( # Allowable URL chars (all)
+ # RFC2732 uses '[...]' for IPv6 addressing - [] are now allowed
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 0 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 16 - 31
+ 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 32 - 47
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, # 48 - 63
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 64 - 79
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, # 80 - 95
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 96 - 111
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, # 112 - 127
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 128 - 143
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 144 - 159
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 160 - 175
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 176 - 191
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 192 - 207
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 208 - 223
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 224 - 239
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 240 - 255
+ )
+
+ URLSchemeDoubleSlash = ("http", "https", "webcal",)
+
+ def __init__(self, url=None, scheme=None, server=None, path=None, extended=None, decode=False):
+
+ self.scheme = ""
+ self.server = ""
+ self.path = ""
+ self.extended = ""
+
+ if not url:
+ self.scheme = scheme
+ self.server = server
+ self.path = path
+ if self.path and decode:
+ self.path = urllib.unquote(self.path)
+ self.extended = extended
+ if self.extended and decode:
+ self.extended = urllib.unquote_plus(self.extended)
+ else:
+ self._parse(url, decode)
+
+ def __str__(self):
+ return "URL: %s" % (self.toString(),)
+
+ def __repr__(self):
+ return "URL: %s" % (self.toString(),)
+
+ def __cmp__(self, other):
+ return cmp(self.toString(), other.toString())
+
+ def absoluteURL(self):
+ return self.toString()
+
+ def relativeURL(self):
+ return self.toString(conversion=URL.eRelative)
+
+ def toString(self, conversion=eAbsolute, encode=True):
+
+ result = ""
+
+ # Add scheme & host if not relative
+ if conversion == URL.eAbsolute and self.scheme and self.server:
+ result += self.scheme + ":"
+ if self.scheme in URL.URLSchemeDoubleSlash:
+ result += "//"
+ result += self.server
+
+ # Get path (or last part of it if required)
+ if self.path and conversion == URL.eLastPath:
+ path = self.path[self.path.rfind("/"):]
+ else:
+ path = self.path
+
+ # Now encode if required
+ if path:
+ result += (urllib.quote(path) if encode else path)
+
+ if self.extended:
+ result += (urllib.quote_plus(self.extended, "?=") if encode else self.extended)
+
+ return result
+
+ def equal(self, comp):
+ # Compare each component
+
+ if self.scheme != comp.scheme:
+ return False
+
+ if self.server != comp.server:
+ return False
+
+ # Ignore trailing slash
+ if self.path.rstrip("/") != comp.path.rstrip("/"):
+ return False
+
+ return True
+
+ def equalRelative(self, comp):
+ # Must be relative
+ if comp.server:
+ return False
+
+ # Just compare paths, ignore trailing slash
+ return self.path.rstrip("/") == comp.path.rstrip("/")
+
+ def dirname(self):
+ if self.path:
+ newpath = os.path.dirname(self.path.rstrip("/")) + "/"
+ return URL(scheme=self.scheme, server=self.server, path=newpath)
+
+ def _parse(self, url, decode=False):
+
+ # Strip off main scheme
+ if url.lower().startswith("url:"):
+ url = url[4:]
+
+ # Special - if it starts with "/" its a relative HTTP url
+ if url[0] == '/':
+ self.scheme = "http"
+ self.server = None
+ self._parsePath(url, decode)
+ else:
+ # Get protocol scheme
+ self.scheme = url[:url.find(":")].lower()
+ url = url[len(self.scheme):]
+
+ if self.scheme in URL.URLSchemeDoubleSlash:
+
+ assert(url.startswith("://"))
+
+ # Look for server
+ splits = url[3:].split("/", 1)
+ self.server = splits[0]
+ if len(splits) == 2:
+ self._parsePath("/" + splits[1], decode)
+
+ elif self.scheme in ("mailto", "urn",):
+
+ assert(url.startswith(":"))
+
+ # Look for server
+ self.server = url[1:]
+
+ def _parsePath(self, path, decode=False):
+
+ # Look for extended bits
+ splits = path.split("?", 1)
+ self.path = splits[0]
+ if decode:
+ self.path = urllib.unquote(self.path)
+ if len(splits) == 2:
+ self.extended = "?" + splits[1]
+ if decode:
+ self.extended = urllib.unquote_plus(self.extended)
Added: CalDAVClientLibrary/trunk/src/protocol/utils/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/utils/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/utils/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2007-2008 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.
+##
Added: CalDAVClientLibrary/trunk/src/protocol/utils/xmlhelpers.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/utils/xmlhelpers.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/utils/xmlhelpers.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,107 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from xml.etree.ElementTree import ElementTree
+from xml.etree.ElementTree import Comment
+from xml.etree.ElementTree import _escape_cdata
+from xml.etree.ElementTree import ProcessingInstruction
+from xml.etree.ElementTree import QName
+from xml.etree.ElementTree import fixtag
+from xml.etree.ElementTree import _raise_serialization_error
+from xml.etree.ElementTree import _encode
+from xml.etree.ElementTree import _escape_attrib
+from StringIO import StringIO
+from xml.etree.ElementTree import SubElement
+
+def SubElementWithData(parent, tag, data=None, attrs={}):
+ element = SubElement(parent, tag, attrs)
+ if data:
+ element.text = data
+ return element
+
+class BetterElementTree(ElementTree):
+
+ def writeUTF8(self, file):
+ assert self._root is not None
+ if not hasattr(file, "write"):
+ file = open(file, "wb")
+ encoding = "utf-8"
+ file.write("<?xml version='1.0' encoding='%s'?>" % encoding)
+ self._prettywrite(file, self._root, encoding, {})
+ file.write("\r\n")
+
+ def _prettywrite(self, file, node, encoding, namespaces, depth=0):
+ # write XML to file
+ tag = node.tag
+ if tag is Comment:
+ file.write("\r\n" + " " * depth)
+ file.write("<!-- %s -->" % _escape_cdata(node.text, encoding))
+ elif tag is ProcessingInstruction:
+ file.write("\r\n" + " " * depth)
+ file.write("<?%s?>" % _escape_cdata(node.text, encoding))
+ else:
+ items = node.items()
+ xmlns_items = [] # new namespaces in this scope
+ try:
+ if isinstance(tag, QName) or tag[:1] == "{":
+ tag, xmlns = fixtag(tag, namespaces)
+ if xmlns: xmlns_items.append(xmlns)
+ except TypeError:
+ _raise_serialization_error(tag)
+ file.write("\r\n" + " " * depth)
+ file.write("<" + _encode(tag, encoding))
+ if items or xmlns_items:
+ items.sort() # lexical order
+ for k, v in items:
+ try:
+ if isinstance(k, QName) or k[:1] == "{":
+ k, xmlns = fixtag(k, namespaces)
+ if xmlns: xmlns_items.append(xmlns)
+ except TypeError:
+ _raise_serialization_error(k)
+ try:
+ if isinstance(v, QName):
+ v, xmlns = fixtag(v, namespaces)
+ if xmlns: xmlns_items.append(xmlns)
+ except TypeError:
+ _raise_serialization_error(v)
+ file.write(" %s=\"%s\"" % (_encode(k, encoding),
+ _escape_attrib(v, encoding)))
+ for k, v in xmlns_items:
+ file.write(" %s=\"%s\"" % (_encode(k, encoding),
+ _escape_attrib(v, encoding)))
+ if node.text or len(node):
+ file.write(">")
+ if node.text:
+ file.write(_escape_cdata(node.text, encoding))
+ for n in node:
+ self._prettywrite(file, n, encoding, namespaces, depth=depth+1)
+ if not node.text or len(node):
+ file.write("\r\n" + " " * depth)
+ file.write("</" + _encode(tag, encoding) + ">")
+ else:
+ file.write(" />")
+ for k, v in xmlns_items:
+ del namespaces[v]
+ if node.tail:
+ file.write(_escape_cdata(node.tail, encoding))
+
+def elementToString(element):
+ os = StringIO()
+ xmldoc = BetterElementTree(element)
+ xmldoc.writeUTF8(os)
+ return os.getvalue()
+
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,32 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.propfindparser import PropFindParser
+from protocol.webdav.definitions import davxml
+
+PropFindParser.textProperties.add(davxml.creationdate)
+PropFindParser.textProperties.add(davxml.displayname)
+PropFindParser.textProperties.add(davxml.getcontentlanguage)
+PropFindParser.textProperties.add(davxml.getcontentlength)
+PropFindParser.textProperties.add(davxml.getcontenttype)
+PropFindParser.textProperties.add(davxml.getetag)
+PropFindParser.textProperties.add(davxml.getlastmodified)
+
+PropFindParser.hrefListProperties.add(davxml.principal_collection_set)
+PropFindParser.hrefProperties.add(davxml.principal_URL)
+PropFindParser.hrefListProperties.add(davxml.alternate_URI_set)
+PropFindParser.hrefListProperties.add(davxml.group_member_set)
+PropFindParser.hrefListProperties.add(davxml.group_membership)
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/ace.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/ace.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/ace.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,169 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from xml.etree.ElementTree import QName
+from protocol.webdav.definitions import davxml
+from xml.etree.ElementTree import SubElement
+
+class ACE(object):
+
+ def __init__(self):
+
+ self.principal = None
+ self.data = None
+ self.invert = False
+ self.grant = True
+ self.privs = ()
+ self.protected = False
+ self.inherited = False
+
+ def getPrincipal(self):
+ return self.principal
+
+ def setPrincipal(self, principal, data=None):
+ self.principal = principal
+ self.data = data
+
+ def canChange(self):
+ return not self.protected and not self.inherited
+
+ @staticmethod
+ def parseFromACL(aclnode):
+
+ aces = []
+ acenodes = aclnode.findall(str(davxml.ace))
+ for node in acenodes:
+ newace = ACE()
+ newace.parseACE(node)
+ aces.append(newace)
+ return aces
+
+ def parseACE(self, acenode):
+
+ assert(acenode and acenode.tag == davxml.ace)
+
+ # Get invert
+ self.invert = False
+ principal_parent = acenode
+ invert = acenode.find(str(davxml.invert))
+ if invert:
+ self.invert = True
+ principal_parent = invert
+
+ # Get the principal
+ principal = principal_parent.find(str(davxml.principal))
+ if not principal or len(principal.getchildren()) != 1:
+ return False
+
+ # Determine principal info
+ child = principal.getchildren()[0]
+ if child.tag == davxml.href:
+ self.setPrincipal(child.tag, child.text)
+
+ elif child.tag in (davxml.all, davxml.authenticated, davxml.unauthenticated, davxml.self,):
+ self.setPrincipal(child.tag)
+
+ elif child.tag == davxml.property:
+ if len(child.getchildren()) == 1:
+ self.setPrincipal(child.tag, QName(child.getchildren()[0].tag))
+ else:
+ self.setPrincipal(child.tag)
+
+ # Determine rights
+ self.grant = True
+ child = acenode.find(str(davxml.grant))
+ if not child:
+ child = acenode.find(str(davxml.deny))
+ if child:
+ self.grant = False
+ if child:
+ self.parsePrivileges(child)
+
+ # Determine protected/inherited state
+ self.protected = acenode.find(str(davxml.protected)) is not None
+ self.inherited = acenode.find(str(davxml.inherited)) is not None
+
+ return True
+
+ def parsePrivileges(self, parent):
+
+ assert(parent.tag in (davxml.grant, davxml.deny,))
+
+ # Parent node contains one of more privilege nodes which we parse
+ self.privs = ()
+ for privilege in parent.getchildren():
+ # Look for privilege
+ if privilege.tag != davxml.privilege or len(privilege.getchildren()) != 1:
+ continue
+
+ # Now get rights within the privilege
+ self.privs += (privilege.getchildren()[0].tag,)
+
+ def generateACE(self, aclnode):
+ # Structure of ace is:
+ #
+ # <DAV:ace>
+ # <DAV:principal>...</DAV:principal>
+ # <DAV:grant>...</DAV:grant>
+ # </DAV:ace>
+
+ # <DAV:ace> element
+ ace = SubElement(aclnode, davxml.ace)
+
+ if self.invert:
+ invert = SubElement(ace, davxml.invert)
+
+ # <DAV:principal> element
+ principal = SubElement(invert if self.invert else ace, davxml.principal)
+
+ # Principal type
+ if self.principal == davxml.href:
+
+ # <DAV:href> element
+ href = SubElement(principal, davxml.href)
+ href.text = self.data
+
+ elif self.principal in (davxml.all, davxml.authenticated, davxml.unauthenticated, davxml.self,):
+
+ # <DAV:all>/<DAV:authenticated>/<DAV:unauthenticated>/<DAV:self> elements
+ SubElement(principal, self.principal)
+
+ elif self.principal == davxml.property:
+
+ # <DAV:property> element - the UID is the property element name
+ property = SubElement(principal, davxml.property)
+ SubElement(property, self.data)
+
+ # Do grant rights for each one set
+ if self.grant:
+ # <DAV:grant> element
+ privs = SubElement(ace, davxml.grant)
+
+ # Do deny rights for each one set
+ else:
+ # <DAV:deny> element
+ privs = SubElement(ace, davxml.deny)
+
+ for item in self.privs:
+ priv = SubElement(privs, davxml.privilege)
+ SubElement(priv, item)
+
+ # <DAV:protected> and <DAV:inherited>
+ if self.protected:
+ SubElement(ace, davxml.protected)
+ if self.inherited:
+ SubElement(ace, davxml.inherited)
+
\ No newline at end of file
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/acl.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/acl.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/acl.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,67 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.requestresponse import RequestResponse
+from protocol.webdav.definitions import methods
+from StringIO import StringIO
+from protocol.http.data.string import RequestDataString
+from protocol.webdav.definitions import davxml
+from xml.etree.ElementTree import Element
+from protocol.utils.xmlhelpers import BetterElementTree
+
+class ACL(RequestResponse):
+
+ def __init__(self, session, url, acls):
+ super(ACL, self).__init__(session, methods.ACL, url)
+ self.acls = acls
+
+ self.initRequestData()
+
+ def initRequestData(self):
+ # Write XML info to a string
+ os = StringIO()
+ self.generateXML(os)
+ self.request_data = RequestDataString(os.getvalue(), "text/xml charset=utf-8")
+
+ def generateXML(self, os):
+ # Structure of document is:
+ #
+ # <DAV:acl>
+ # <DAV:ace>
+ # <S:inheritable xmlns:S="http:#jakarta.apache.org/slide/"> / <S:non-inheritable xmlns:S="http:#jakarta.apache.org/slide/">
+ # <DAV:principal>...</DAV:principal>
+ # <DAV:grant>...</DAV:grant>
+ # </DAV:ace>
+ # ...
+ # </DAV:acl>
+
+ # <DAV:acl> element
+ acl = Element(davxml.acl)
+
+ # Do for each ACL
+ if self.acls:
+
+ for ace in self.acls:
+ # Cannot do if change not allowed
+ if not ace.canChange():
+ continue
+
+ # <DAV:ace> element
+ ace.generateACE(acl)
+
+ # Now we have the complete document, so write it out (no indentation)
+ xmldoc = BetterElementTree(acl)
+ xmldoc.writeUTF8(os)
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/copy.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/copy.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/copy.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,22 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.copymovebase import CopyMoveBase
+
+class Copy(CopyMoveBase):
+
+ def __init__(self, session, url_old, absurl_new, overwrite=False):
+ super(Copy, self).__init__(session, url_old, absurl_new, overwrite=overwrite, delete_original=False)
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/copymovebase.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/copymovebase.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/copymovebase.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,45 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.requestresponse import RequestResponse
+from protocol.webdav.definitions import methods
+from protocol.webdav.definitions import headers
+
+class CopyMoveBase(RequestResponse):
+
+ def __init__(self, session, url_old, absurl_new, overwrite=False, delete_original=True):
+ super(CopyMoveBase, self).__init__(session, methods.MOVE if delete_original else methods.COPY, url_old)
+ self.absurl_new = absurl_new
+ self.overwrite = overwrite
+
+ def setData(self, etag):
+ self.request_data = None
+ self.response_data = None
+
+ # Must have matching ETag
+ if etag:
+ self.etag = etag
+ self.etag_match = True
+
+ def addHeaders(self, hdrs):
+ # Do default
+ super(CopyMoveBase, self).addHeaders(hdrs)
+
+ # Add Destination header
+ hdrs.append((headers.Destination, self.absurl_new))
+
+ # Add Overwrite header
+ hdrs.append((headers.Overwrite, headers.OverwriteTrue if self.overwrite else headers.OverwriteFalse))
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2007-2008 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.
+##
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/davxml.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/davxml.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/davxml.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,87 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from xml.etree.ElementTree import QName
+
+DAVNamespace = "DAV:"
+
+propfind = QName(DAVNamespace, "propfind")
+propname = QName(DAVNamespace, "propname")
+allprop = QName(DAVNamespace, "allprop")
+prop = QName(DAVNamespace, "prop")
+propstat = QName(DAVNamespace, "propstat")
+propertyupdate = QName(DAVNamespace, "propertyupdate")
+remove = QName(DAVNamespace, "remove")
+set = QName(DAVNamespace, "set")
+
+getetag = QName(DAVNamespace, "getetag")
+creationdate = QName(DAVNamespace, "creationdate")
+displayname = QName(DAVNamespace, "displayname")
+getcontentlanguage = QName(DAVNamespace, "getcontentlanguage")
+getcontentlength = QName(DAVNamespace, "getcontentlength")
+getcontenttype = QName(DAVNamespace, "getcontenttype")
+getlastmodified = QName(DAVNamespace, "getlastmodified")
+resourcetype = QName(DAVNamespace, "resourcetype")
+collection = QName(DAVNamespace, "collection")
+
+lockinfo = QName(DAVNamespace, "lockinfo")
+lockscope = QName(DAVNamespace, "lockscope")
+locktype = QName(DAVNamespace, "locktype")
+owner = QName(DAVNamespace, "owner")
+exclusive = QName(DAVNamespace, "exclusive")
+shared = QName(DAVNamespace, "shared")
+write = QName(DAVNamespace, "write")
+
+acl = QName(DAVNamespace, "acl")
+ace = QName(DAVNamespace, "ace")
+invert = QName(DAVNamespace, "invert")
+principal = QName(DAVNamespace, "principal")
+privilege = QName(DAVNamespace, "privilege")
+grant = QName(DAVNamespace, "grant")
+deny = QName(DAVNamespace, "deny")
+protected = QName(DAVNamespace, "protected")
+inherited = QName(DAVNamespace, "inherited")
+
+href = QName(DAVNamespace, "href")
+all = QName(DAVNamespace, "all")
+authenticated = QName(DAVNamespace, "authenticated")
+unauthenticated = QName(DAVNamespace, "unauthenticated")
+property = QName(DAVNamespace, "property")
+self = QName(DAVNamespace, "self")
+read = QName(DAVNamespace, "read")
+write = QName(DAVNamespace, "write")
+write_properties = QName(DAVNamespace, "write-properties")
+write_content = QName(DAVNamespace, "write-content")
+read_acl = QName(DAVNamespace, "read-acl")
+read_current_user_privilege_set = QName(DAVNamespace, "read-current-user-privilege-set")
+write_acl = QName(DAVNamespace, "write-acl")
+bind = QName(DAVNamespace, "bind")
+unbind = QName(DAVNamespace, "unbind")
+all = QName(DAVNamespace, "all")
+
+multistatus = QName(DAVNamespace, "multistatus")
+response = QName(DAVNamespace, "response")
+responsedescription = QName(DAVNamespace, "responsedescription")
+status = QName(DAVNamespace, "status")
+
+principal_match = QName(DAVNamespace, "principal-match")
+
+principal_collection_set = QName(DAVNamespace, "principal-collection-set")
+
+alternate_URI_set = QName(DAVNamespace, "alternate-URI-set")
+principal_URL = QName(DAVNamespace, "principal-URL")
+group_member_set = QName(DAVNamespace, "group-member-set")
+group_membership = QName(DAVNamespace, "group-membership")
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/headers.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/headers.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/headers.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,39 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+# RFC2518 9 - Request Header fields (only the ones we need)
+
+from protocol.http.definitions.headers import * #@UnusedWildImport
+
+DAV = "DAV"
+DAV1 = "1"
+DAV2 = "2"
+DAVbis = "bis"
+DAVACL = "access-control" # ACL extension RFC3744
+Depth = "Depth"
+Depth0 = "0"
+Depth1 = "1"
+DepthInfinity = "infinity"
+Destination = "Destination"
+If = "If"
+ForceAuthentication = "Force-Authentication"
+LockToken = "Lock-Token"
+Overwrite = "Overwrite"
+OverwriteTrue = "T"
+OverwriteFalse = "F"
+Timeout = "Timeout"
+TimeoutSeconds = "Second-"
+TimeoutInfinite = "Infinite"
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/methods.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/methods.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/methods.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,29 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.http.definitions.methods import * #@UnusedWildImport
+
+# RFC2518 - WebDAV Request Methods
+
+MKCOL = "MKCOL"
+MOVE = "MOVE"
+COPY = "COPY"
+PROPFIND = "PROPFIND"
+PROPPATCH = "PROPPATCH"
+LOCK = "LOCK"
+UNLOCK = "UNLOCK"
+REPORT = "REPORT" # RFC3253
+ACL = "ACL" # RFC3744
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/statuscodes.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/statuscodes.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/definitions/statuscodes.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,24 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.http.definitions.statuscodes import * #@UnusedWildImport
+
+Processing = 102
+MultiStatus = 207
+UnprocessableEntity = 422
+Locked = 423
+FailedDependency = 424
+InsufficientStorage = 507
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/delete.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/delete.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/delete.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,32 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.requestresponse import RequestResponse
+from protocol.webdav.definitions import methods
+
+class Delete(RequestResponse):
+
+ def __init__(self, session, url):
+ super(Delete, self).__init__(session, methods.DELETE, url)
+
+ def setData(self, etag=None):
+ self.request_data = None
+ self.response_data = None
+
+ # Must have matching ETag
+ if etag:
+ self.etag = etag
+ self.etag_match = True
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/get.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/get.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/get.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,22 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.getbase import GetBase
+
+class Get(GetBase):
+
+ def __init__(self, session, url, lock=None):
+ super(Get, self).__init__(session, url, lock=lock, head=False)
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/getbase.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/getbase.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/getbase.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,50 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.requestresponse import RequestResponse
+from protocol.webdav.definitions import methods
+from protocol.webdav.definitions import headers
+
+
+class GetBase(RequestResponse):
+
+ def __init__(self, session, url, lock=None, head=False):
+ super(GetBase, self).__init__(session, methods.HEAD if head else methods.GET, url, lock=lock)
+ self.head = head
+
+ def setData(self, response_data, etag=None):
+ self.request_data = None
+ self.response_data = response_data
+
+ # Must have matching ETag
+ if etag:
+ self.etag = etag
+ self.etag_match = True
+
+ def getNewETag(self):
+ # Get the ETag header from response headers
+ if self.hasResponseHeader(headers.ETag):
+ return self.getResponseHeader(headers.ETag)
+ else:
+ return None
+
+ def getContentLength(self):
+ # Always zero to prevent attempt to read response
+ return 0 if self.head else self.content_length
+
+ def getChunked(self):
+ # Always false to prevent attempt to read response
+ return False if self.head else self.chunked
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/head.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/head.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/head.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,22 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.getbase import GetBase
+
+class Head(GetBase):
+
+ def __init__(self, session, url, lock=None):
+ super(Head, self).__init__(session, url, lock=lock, head=True)
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/lock.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/lock.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/lock.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,137 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.requestresponse import RequestResponse
+from protocol.webdav.definitions import methods
+from protocol.webdav.definitions import headers
+from StringIO import StringIO
+from protocol.http.data.string import RequestDataString
+from protocol.webdav.definitions import davxml
+from xml.etree.ElementTree import Element
+from protocol.utils.xmlhelpers import BetterElementTree
+
+class Lock(RequestResponse):
+
+ eExclusive = 0
+ eShared = 1
+
+ eResourceMustExist = 0
+ eResourceMustNotExist = 1
+ eResourceMayExist = 2
+
+ def __init__(self, session, url, depth, scope, owner, timeout, exists=eResourceMustExist):
+
+ assert(depth in (headers.Depth0, headers.Depth1, headers.DepthInfinity))
+ assert(scope in (Lock.eExclusive, Lock.eShared,))
+ assert(exists in (Lock.eResourceMustExist, Lock.eResourceMustNotExist, Lock.eResourceMayExist))
+
+ super(Lock, self).__init__(session, methods.LOCK, url)
+
+ self.depth = depth
+ self.scope = scope
+ self.owner = owner
+ self.timeout = timeout
+
+ # Do appropriate etag based on exists
+ if exists == Lock.eResourceMustExist:
+ self.etag = "*"
+ self.etag_match = True
+ elif exists == Lock.eResourceMustNotExist:
+ self.etag = "*"
+ self.etag_match = False
+ elif exists == Lock.eResourceMayExist:
+ pass
+
+ self.initRequestData()
+
+ def initRequestData(self):
+ # Write XML info to a string
+ os = StringIO()
+ self.generateXML(os)
+ self.request_data = RequestDataString(os.getvalue(), "text/xml charset=utf-8")
+
+ def addHeaders(self, hdrs):
+ # Do default
+ super(Lock, self).addHeaders(hdrs)
+
+ # Add depth header
+ hdrs.append((headers.Depth, self.depth))
+
+ # Add timeout header
+ if self.timeout == -1:
+ hdrs.append((headers.Timeout, headers.TimeoutInfinite))
+ elif self.timeout > 0:
+ hdrs.append((headers.Timeout, "%s%d" % (headers.TimeoutSeconds, self.timeout)))
+
+ def generateXML(self, os):
+ # Structure of document is:
+ #
+ # <DAV:lockinfo>
+ # <DAV:lockscope>
+ # <DAV:exclusive/> | <DAV:shared/>
+ # </DAV:lockscope>
+ # <DAV:locktype>
+ # <DAV:write/>
+ # </DAV:locktype>
+ # <DAV:owner>
+ # <<owner>>
+ # </DAV:owner>
+ # </DAV:lockinfo>
+
+ # <DAV:lockinfo> element
+ lockinfo = Element(davxml.lockinfo)
+
+ # <DAV:lockscope> element
+ lockscope = Element(davxml.lockscope)
+ lockinfo.append(lockscope)
+
+ # <DAV:exclusive/> | <DAV:shared/> element
+ lockscope.append(Element(davxml.exclusive if self.scope == Lock.eExclusive else davxml.shared))
+
+ # <DAV:locktype> element
+ locktype = Element(davxml.locktype)
+ lockinfo.append(locktype)
+
+ # <DAV:write/> element
+ locktype.append(Element(davxml.write))
+
+ # <DAV:owner> element is optional
+ if self.owner:
+ # <DAV:owner> element
+ owner = Element(davxml.owner)
+ owner.text = self.owner
+ lockinfo.append(owner)
+
+ # Now we have the complete document, so write it out (no indentation)
+ BetterElementTree(lockinfo).writeUTF8(os)
+
+ def getLockToken(self):
+
+ # Get the Lock-Token header from response headers
+ result = ""
+ if self.hasResponseHeader(headers.LockToken):
+
+ # Get Coded-URL
+ codedurl = self.getResponseHeader(headers.LockToken)
+
+ # Strip leading/trailing <>
+ codeurl = codedurl.strip()
+ if codeurl.startswith("<") and codeurl.endswith(">"):
+ result = codeurl[1:-1]
+ else:
+ result = codeurl
+
+ return result
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/makecollection.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/makecollection.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/makecollection.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,23 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.requestresponse import RequestResponse
+from protocol.webdav.definitions import methods
+
+class MakeCollection(RequestResponse):
+
+ def __init__(self, session, url):
+ super(MakeCollection, self).__init__(session, methods.MKCOL, url)
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/move.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/move.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/move.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,22 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.copymovebase import CopyMoveBase
+
+class Move(CopyMoveBase):
+
+ def __init__(self, session, url_old, absurl_new, overwrite=False):
+ super(Move, self).__init__(session, url_old, absurl_new, overwrite=overwrite, delete_original=True)
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/multiresponseparser.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/multiresponseparser.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/multiresponseparser.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,36 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.xmlresponseparser import XMLResponseParser
+from protocol.webdav.definitions import davxml
+
+class MultiResponseParser(XMLResponseParser):
+
+ def parse(self, multistatus_node):
+ # Must have a node
+ if not multistatus_node:
+ return
+
+ # Verify that the node is the correct element <DAV:multistatus>
+ if multistatus_node.tag != davxml.multistatus:
+ return
+
+ # Node is the right type, so iterator over all child response nodes and process each one
+ for response in multistatus_node.getchildren():
+ self.parseResponse(response)
+
+ def parseResponse(self, response):
+ raise NotImplementedError
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/options.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/options.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/options.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,51 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.requestresponse import RequestResponse
+from protocol.webdav.definitions import methods
+from protocol.http.util import parsetoken
+from protocol.webdav.definitions import headers
+
+class Options(RequestResponse):
+
+ def __init__(self, session, url):
+ super(Options, self).__init__(session, methods.OPTIONS, url)
+
+ def getAllowed(self):
+
+ methods = ()
+ if self.hasResponseHeader(headers.Allow):
+
+ # Look at each one and accumlate allowed methods
+ for value in self.getResponseHeaders(headers.Allow):
+ while value:
+ token, value = parsetoken(value, ", \t")
+ methods += (token,)
+
+ return methods
+
+ def isAllowed(self, method):
+ methods = ()
+ if self.hasResponseHeader(headers.Allow):
+
+ # Look at each one and accumlate allowed methods
+ for value in self.getResponseHeaders(headers.Allow):
+ while value:
+ token, value = parsetoken(value, ", \t")
+ if method == token:
+ return True
+
+ return False
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/principalmatch.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/principalmatch.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/principalmatch.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,73 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.propfindbase import PropFindBase
+from protocol.webdav.definitions import headers
+from protocol.webdav.definitions import methods
+from StringIO import StringIO
+from protocol.http.data.string import RequestDataString
+from protocol.webdav.definitions import davxml
+from xml.etree.ElementTree import Element
+from xml.etree.ElementTree import SubElement
+from protocol.utils.xmlhelpers import BetterElementTree
+
+class PrincipalMatch(PropFindBase):
+
+ def __init__(self, session, url, props):
+ super(PrincipalMatch, self).__init__(session, url, headers.Depth0)
+ self.props = props
+ self.method = methods.REPORT
+
+ self.initRequestData()
+
+ def initRequestData(self):
+ # Write XML info to a string
+ os = StringIO()
+ self.generateXML(os)
+ self.request_data = RequestDataString(os.getvalue(), "text/xml charset=utf-8")
+
+ def generateXML(self, os):
+ # Structure of document is:
+ #
+ # <DAV:principal-match>
+ # <DAV:self/>
+ # <DAV:prop>
+ # <<names of each property as elements>>
+ # </DAV:prop>
+ # </DAV:principal-match>
+
+ # <DAV:principal-match> element
+ principalmatch = Element(davxml.principal_match)
+
+ # <DAV:self> element
+ SubElement(principalmatch, davxml.self)
+
+ if self.props:
+
+ # <DAV:prop> element
+ prop = SubElement(principalmatch, davxml.prop)
+
+ # Now add each property
+ for item in self.props:
+
+ # Add property element taking namespace into account
+
+ # Look for DAV namespace and reuse that one
+ SubElement(prop, item)
+
+ # Now we have the complete document, so write it out (no indentation)
+ xmldoc = BetterElementTree(principalmatch)
+ xmldoc.writeUTF8(os)
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/propall.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/propall.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/propall.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,43 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.propfindbase import PropFindBase
+from xml.etree.ElementTree import Element
+from protocol.webdav.definitions import davxml
+from protocol.utils.xmlhelpers import BetterElementTree
+
+class PropAll(PropFindBase):
+
+ def __init__(self, session, url, depth):
+ super(PropAll, self).__init__(session, url, depth)
+ self.initRequestData()
+
+ def generateXML(self, os):
+ # Structure of document is:
+ #
+ # <DAV:propfind>
+ # <DAV:allprop/>
+ # </DAV:propfind>
+
+ # <DAV:propfind> element
+ propfind = Element(davxml.propfind)
+
+ # <DAV:propname> element
+ propfind.append(Element(davxml.allprop))
+
+ # Now we have the complete document, so write it out (no indentation)
+ xmldoc = BetterElementTree(propfind)
+ xmldoc.writeUTF8(os)
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/propfind.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/propfind.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/propfind.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,53 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.propfindbase import PropFindBase
+from xml.etree.ElementTree import Element
+from protocol.webdav.definitions import davxml
+from protocol.utils.xmlhelpers import BetterElementTree
+
+class PropFind(PropFindBase):
+
+ def __init__(self, session, url, depth, props):
+ super(PropFind, self).__init__(session, url, depth)
+ self.props = props
+
+ self.initRequestData()
+
+ def generateXML(self, os):
+ # Structure of document is:
+ #
+ # <DAV:propfind>
+ # <DAV:prop>
+ # <<names of each property as elements>>
+ # </DAV:prop>
+ # </DAV:propfind>
+
+ # <DAV:propfind> element
+ propfind = Element(davxml.propfind)
+
+ # <DAV:prop> element
+ prop = Element(davxml.prop)
+ propfind.append(prop)
+
+ # Now add each property
+ for propname in self.props:
+ # Add property element taking namespace into account
+ prop.append(Element(propname))
+
+ # Now we have the complete document, so write it out (no indentation)
+ xmldoc = BetterElementTree(propfind)
+ xmldoc.writeUTF8(os)
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/propfindbase.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/propfindbase.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/propfindbase.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,48 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.requestresponse import RequestResponse
+from protocol.webdav.definitions import methods
+from protocol.webdav.definitions import headers
+from protocol.http.data.string import RequestDataString
+from StringIO import StringIO
+
+class PropFindBase(RequestResponse):
+
+ def __init__(self, session, url, depth):
+ assert(depth in (headers.Depth0, headers.Depth1, headers.DepthInfinity))
+
+ super(PropFindBase, self).__init__(session, methods.PROPFIND, url)
+ self.depth = depth
+
+ def initRequestData(self):
+ # Write XML info to a string
+ os = StringIO()
+ self.generateXML(os);
+ self.request_data = RequestDataString(os.getvalue(), "text/xml; charset=utf-8");
+
+ def setOutput(self, response_data):
+ self.response_data = response_data
+
+ def addHeaders(self, hdrs):
+ # Do default
+ super(PropFindBase, self).addHeaders(hdrs)
+
+ # Add depth header
+ hdrs.append((headers.Depth, self.depth))
+
+ def generateXML(self, os):
+ raise NotImplementedError
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/propfindparser.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/propfindparser.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/propfindparser.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,147 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.multiresponseparser import MultiResponseParser
+from protocol.webdav.definitions import davxml
+from protocol.http.util import parseStatusLine
+from xml.etree.ElementTree import QName
+from protocol.url import URL
+
+class PropFindParser(MultiResponseParser):
+
+ textProperties = set()
+ hrefProperties = set()
+ hrefListProperties = set()
+
+ class PropFindResult(object):
+
+ def __init__(self):
+ self.textProperties = {}
+ self.hrefProperties = {}
+ self.nodeProperties = {}
+ self.badProperties = {}
+
+ def setResource(self, resource):
+ self.resource = resource
+ def getResource(self):
+ return self.resource
+
+ def addTextProperty(self, name, value):
+ self.textProperties[name] = value
+ def getTextProperties(self):
+ return self.textProperties
+
+ def addHrefProperty(self, name, value):
+ self.hrefProperties[name] = value
+ def getHrefProperties(self):
+ return self.hrefProperties
+
+ def addNodeProperty(self, name, node):
+ self.nodeProperties[name] = node
+ def getNodeProperties(self):
+ return self.nodeProperties
+
+ def addBadProperty(self, name, status):
+ self.badProperties[name] = status
+ def getBadProperties(self):
+ return self.badProperties
+
+ def __init__(self):
+ self.results = {}
+
+ def getResults(self):
+ return self.results
+
+ # Parse the response element down to the properties
+ def parseResponse(self, response):
+ # Verify that the node is the correct element <DAV:response>
+ if response.tag != davxml.response:
+ return
+
+ # Node is the right type, so iterate over all child response nodes and process each one
+ result = PropFindParser.PropFindResult()
+ for child in response.getchildren():
+
+ # Is it the href
+ if child.tag == davxml.href:
+ result.setResource(child.text)
+
+ # Is it propstat
+ elif child.tag == davxml.propstat:
+ self.parsePropStat(child, result)
+
+ # Add the resource only if we got one
+ if result.getResource():
+ self.results[result.getResource()] = result
+
+ def parsePropStat(self, propstat, result):
+ # Scan the propstat node the status - we only process OK status
+
+ # Now look for a <DAV:status> element in its children
+ status = propstat.find(str(davxml.status))
+
+ # Now parse the response and dispatch accordingly
+ if status is not None:
+
+ status_result = parseStatusLine(status.text)
+ badstatus = status_result if status_result / 100 != 2 else None
+
+ # Now look for a <DAV:prop> element in its children
+ for item in propstat.findall(str(davxml.prop)):
+ self.parseProp(item, result, badstatus)
+
+ def parseProp(self, property, result, badstatus):
+ # Scan the prop node - each child is processed
+ for item in property.getchildren():
+ self.parsePropElement(item, result, badstatus)
+
+ # Parsing of property elements
+ def parsePropElement(self, prop, result, badstatus):
+ # Here we need to detect the type of element and dispatch accordingly
+ if badstatus:
+ result.addBadProperty(QName(prop.tag), badstatus)
+
+ elif prop.tag in PropFindParser.textProperties:
+ self.parsePropElementText(prop, result)
+
+ elif prop.tag in PropFindParser.hrefProperties:
+ self.parsePropElementHref(prop, result, False)
+
+ elif prop.tag in PropFindParser.hrefListProperties:
+ self.parsePropElementHref(prop, result, True)
+
+ else:
+ self.parsePropElementUnknown(prop, result)
+
+ def parsePropElementText(self, prop, result):
+ # Grab the element data
+ result.addTextProperty(QName(prop.tag), prop.text if prop.text else "")
+ result.addNodeProperty(QName(prop.tag), prop)
+
+ def parsePropElementHref(self, prop, result, is_list):
+ # Grab the element data
+ hrefs = tuple([URL(url=href.text, decode=True) for href in prop.findall(str(davxml.href))])
+ if not is_list:
+ if len(hrefs) == 1:
+ hrefs = hrefs[0]
+ else:
+ hrefs = ""
+ result.addHrefProperty(QName(prop.tag), hrefs)
+ result.addNodeProperty(QName(prop.tag), prop)
+
+ def parsePropElementUnknown(self, prop, result):
+ # Just add the node
+ result.addNodeProperty(QName(prop.tag), prop)
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/propnames.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/propnames.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/propnames.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,43 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.propfindbase import PropFindBase
+from xml.etree.ElementTree import Element
+from protocol.webdav.definitions import davxml
+from protocol.utils.xmlhelpers import BetterElementTree
+
+class PropNames(PropFindBase):
+
+ def __init__(self, session, url, depth):
+ super(PropNames, self).__init__(session, url, depth)
+ self.initRequestData()
+
+ def generateXML(self, os):
+ # Structure of document is:
+ #
+ # <DAV:propfind>
+ # <DAV:propname/>
+ # </DAV:propfind>
+
+ # <DAV:propfind> element
+ propfind = Element(davxml.propfind)
+
+ # <DAV:propname> element
+ propfind.append(Element(davxml.propname))
+
+ # Now we have the complete document, so write it out (no indentation)
+ xmldoc = BetterElementTree(propfind)
+ xmldoc.writeUTF8(os)
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/proppatch.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/proppatch.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/proppatch.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,75 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from xml.etree.ElementTree import Element
+from protocol.webdav.definitions import davxml
+from protocol.utils.xmlhelpers import BetterElementTree
+from protocol.webdav.requestresponse import RequestResponse
+from xml.etree.ElementTree import SubElement
+from protocol.webdav.definitions import methods
+from protocol.http.data.string import RequestDataString
+from StringIO import StringIO
+
+class PropPatch(RequestResponse):
+
+ def __init__(self, session, url, setprops=None, delprops=None):
+ super(PropPatch, self).__init__(session, methods.PROPPATCH, url)
+ self.setprops = setprops
+ self.delprops = delprops
+
+ self.initRequestData()
+
+ def initRequestData(self):
+ # Write XML info to a string
+ os = StringIO()
+ self.generateXML(os);
+ self.request_data = RequestDataString(os.getvalue(), "text/xml; charset=utf-8");
+
+ def setOutput(self, response_data):
+ self.response_data = response_data
+
+ def generateXML(self, os):
+ # Structure of document is:
+ #
+ # <DAV:propertyupdate>
+ # <DAV:set>
+ # <<names/values of each property as elements>>
+ # </DAV:set>
+ # <DAV:remove>
+ # <<names of each property as elements>>
+ # </DAV:remove>
+ # </DAV:propertyupdate>
+
+ # <DAV:propertyupdate> element
+ propertyupdate = Element(davxml.propertyupdate)
+
+ # <DAV:set> element
+ if self.setprops:
+ set = SubElement(propertyupdate, davxml.set)
+ propel = SubElement(set, davxml.prop)
+ for prop in self.setprops:
+ propel.append(prop)
+
+ # <DAV:remove> element
+ if self.delprops:
+ remove = SubElement(propertyupdate, davxml.remove)
+ propel = SubElement(remove, davxml.prop)
+ for prop in self.delprops:
+ propel.append(prop)
+
+ # Now we have the complete document, so write it out (no indentation)
+ xmldoc = BetterElementTree(propertyupdate)
+ xmldoc.writeUTF8(os)
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/put.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/put.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/put.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,46 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.requestresponse import RequestResponse
+from protocol.webdav.definitions import methods
+from protocol.webdav.definitions import headers
+
+class Put(RequestResponse):
+
+ def __init__(self, session, url, lock=None):
+ super(Put, self).__init__(session, methods.PUT, url, lock=lock)
+
+ def setData(self, request_data, response_data, etag=None, new_item=False):
+ assert(not (etag and new_item))
+ self.request_data = request_data
+ self.response_data = response_data
+
+ # Must have matching ETag
+ if etag:
+ self.etag = etag
+ self.etag_match = True
+
+ # ETag should be '*' and we add If-None-Match header to ensure we do not overwrite something already there
+ if new_item:
+ self.etag = "*"
+ self.etag_match = False
+
+ def getNewETag(self):
+ # Get the ETag header from response headers
+ if self.hasResponseHeader(headers.ETag):
+ return self.getResponseHeader(headers.ETag)
+ else:
+ return None
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/report.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/report.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/report.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,26 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.requestresponse import RequestResponse
+from protocol.webdav.definitions import methods
+
+class Report(RequestResponse):
+
+ def __init__(self, session, url):
+ super(Report, self).__init__(session, methods.REPORT, url)
+
+ def _setOutput(self, response_data):
+ self.response_data = response_data;
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/requestresponse.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/requestresponse.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/requestresponse.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,37 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.http.requestresponse import RequestResponse as HTTPRequestResponse
+from protocol.http.requestresponse import RequestResponse
+from protocol.webdav.definitions import headers
+
+class RequestResponse(HTTPRequestResponse):
+
+ def __init__(self, session, method, ruri, etag=None, etag_match=False, lock=None):
+ super(RequestResponse, self).__init__(session, method, ruri, etag, etag_match)
+ self.lock = lock
+
+ def setLock(self, lock):
+ self.lock = lock
+
+ def addHeaders(self, hdrs):
+ # Do inherited
+ super(RequestResponse, self).addHeaders(hdrs)
+
+ # Do Lock matching
+ if self.lock:
+ # This is an untagged token - i.e. it applies to the resource being addressed
+ hdrs.append((headers.If, "(<%s>)" % (self.lock,)))
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/session.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/session.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/session.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,141 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.http.session import Session as HTTPSession
+from protocol.http.requestresponse import RequestResponse
+from protocol.http.data.string import ResponseDataString
+from protocol.http.definitions import methods
+from protocol.http.definitions import statuscodes
+from protocol.webdav.definitions import headers
+
+class Session(HTTPSession):
+
+ def __init__(self, server, port=None, ssl=False, log=None):
+ super(Session, self).__init__(server, port, ssl, log)
+ self.initialised = False
+ self.version = ()
+
+ def initialise(self, host, base_uri):
+ # Set host change
+ self.setServer(host)
+
+ # Loop repeating until we can do it or get a fatal error
+ first_time = True
+ while True:
+
+ # Create OPTIONS request for the base_uri
+ request = RequestResponse(self, methods.OPTIONS, base_uri)
+ request.setSession(self)
+ sout = ResponseDataString()
+ request.setData(None, sout)
+
+ # Add request and process it
+ self.sendRequest(request)
+
+ # Check response
+ if request.getStatusCode() == statuscodes.Unauthorized:
+
+ # If we had authorization before, then chances are auth details are wrong - so delete and try again with new auth
+ if self.hasAuthorization():
+
+ self.authorization = None
+
+ # Display error so user knows why the prompt occurs again
+ self.displayHTTPError(request)
+
+ # Get authorization object (prompt the user) and redo the request
+ self.authorization, cancelled = self.getAuthorizor(first_time, request.getResponseHeaders(headers.WWWAuthenticate))
+
+ # Check for auth cancellation
+ if cancelled:
+
+ self.authorization = None
+ return False
+
+ first_time = False
+
+ # Repeat the request loop with new authorization
+ continue
+
+ # Look for success and exit loop for further processing
+ if request.getStatusCode() in (statuscodes.OK, statuscodes.NoContent):
+
+ # Grab the server string
+ if request.hasResponseHeader(headers.Server):
+ self.setServerDescriptor = self.setServerDescriptor(request.getResponseHeader(headers.Server))
+
+ # Now check the response headers for a DAV version (may be more than one)
+ self.version = ()
+ for dav_version in request.getResponseHeaders(headers.DAV):
+
+ # Tokenize on commas
+ for token in dav_version.split(","):
+
+ token = token.strip()
+ self.addVersion(token)
+
+ self.setServerType(self.version)
+
+ # Put other strings into capability
+ capa = ""
+ for name, value in request.getResponseHeaders().iteritems():
+
+ if (not name.lower().startswith(headers.Server) and
+ not name.lower().startswith(headers.Date) and
+ name.lower().startswith("Content-")):
+
+ capa += "%s: %s\n" % (name, value,)
+
+ self.setServerCapability(capa)
+
+ # Just assume any version is fine for now
+ break
+
+ # If we get here we had some kind of fatal error
+ self.handleHTTPError(request)
+ return False
+
+ self.initialised = True
+
+ return True
+
+ def addVersion(self, token):
+ self.version += (token,)
+
+ def hasDAVVersion(self, version):
+ return version in self.version
+
+ def hasDAV(self):
+ return self.hasDAVVersion(headers.DAV1)
+
+ def hasDAVLocking(self):
+ return self.hasDAVVersion(headers.DAV2) or self.hasDAVVersion(headers.DAVbis)
+
+ def hasDAVACL(self):
+ return self.hasDAVVersion(headers.DAVACL)
+
+ def getAuthorizor(self, first_time, www_authenticate):
+ raise NotImplementedError
+
+ def setServerType(self, type):
+ raise NotImplementedError
+
+ def setServerDescriptor(self, txt):
+ raise NotImplementedError
+
+ def setServerCapability(self, txt):
+ raise NotImplementedError
+
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2007-2008 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.
+##
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_ace.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_ace.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_ace.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,134 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.ace import ACE
+from xml.etree.ElementTree import XML
+from protocol.webdav.definitions import davxml
+from xml.etree.ElementTree import Element
+from protocol.utils.xmlhelpers import BetterElementTree
+from StringIO import StringIO
+
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def verifyXML(self, x, y=None):
+
+ x = x.replace("\n", "\r\n")
+ if y:
+ y = y.replace("\n", "\r\n")
+
+ # Parse the XML data
+ a = ACE()
+ a.parseACE(XML(x))
+
+ # Generate the XML data
+ aclnode = Element(davxml.acl)
+ a.generateACE(aclnode)
+ os = StringIO()
+ xmldoc = BetterElementTree(aclnode.getchildren()[0])
+ xmldoc.writeUTF8(os)
+
+ # Verify data
+ self.assertEqual(os.getvalue(), y if y else x)
+
+ def test_XML1(self):
+
+ self.verifyXML("""<?xml version='1.0' encoding='utf-8'?>
+<ns0:ace xmlns:ns0="DAV:">
+ <ns0:principal>
+ <ns0:href>/principals/users/a</ns0:href>
+ </ns0:principal>
+ <ns0:grant>
+ <ns0:privilege>
+ <ns0:read />
+ </ns0:privilege>
+ </ns0:grant>
+</ns0:ace>
+""")
+
+ def test_XML2(self):
+
+ self.verifyXML("""<?xml version='1.0' encoding='utf-8'?>
+<ns0:ace xmlns:ns0="DAV:">
+ <ns0:principal>
+ <ns0:unauthenticated />
+ </ns0:principal>
+ <ns0:deny>
+ <ns0:privilege>
+ <ns0:read />
+ </ns0:privilege>
+ <ns0:privilege>
+ <ns0:write />
+ </ns0:privilege>
+ </ns0:deny>
+</ns0:ace>
+""")
+
+ def test_XML3(self):
+
+ self.verifyXML("""<?xml version='1.0' encoding='utf-8'?>
+<ns0:ace xmlns:ns0="DAV:">
+ <ns0:principal>
+ <ns0:href>/principals/users/a</ns0:href>
+ </ns0:principal>
+ <ns0:grant>
+ <ns0:privilege>
+ <ns0:read />
+ </ns0:privilege>
+ </ns0:grant>
+ <ns0:protected />
+ <ns0:inherited />
+</ns0:ace>
+""")
+
+ def test_XML4(self):
+
+ self.verifyXML("""<?xml version='1.0' encoding='utf-8'?>
+<ns0:ace xmlns:ns0="DAV:">
+ <ns0:invert>
+ <ns0:principal>
+ <ns0:self />
+ </ns0:principal>
+ </ns0:invert>
+ <ns0:grant>
+ <ns0:privilege>
+ <ns0:read />
+ </ns0:privilege>
+ </ns0:grant>
+ <ns0:protected />
+ <ns0:inherited />
+</ns0:ace>
+""")
+
+ def test_XML5(self):
+
+ self.verifyXML("""<?xml version='1.0' encoding='utf-8'?>
+<ns0:ace xmlns:ns0="DAV:">
+ <ns0:principal>
+ <ns0:property>
+ <ns0:owner />
+ </ns0:property>
+ </ns0:principal>
+ <ns0:grant>
+ <ns0:privilege>
+ <ns0:read />
+ </ns0:privilege>
+ </ns0:grant>
+</ns0:ace>
+""")
+
+
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_acl.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_acl.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_acl.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,43 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.http.session import Session
+from protocol.webdav.acl import ACL
+
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = ACL(server, "/", ())
+ self.assertEqual(request.getMethod(), "ACL")
+
+class TestRequestHeaders(unittest.TestCase):
+ pass
+
+class TestRequestBody(unittest.TestCase):
+ pass
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+ pass
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_copy.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_copy.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_copy.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,72 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.http.session import Session
+from protocol.webdav.copy import Copy
+
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = Copy(server, "/a", "http://www.example.com/b")
+ self.assertEqual(request.getMethod(), "COPY")
+
+class TestRequestHeaders(unittest.TestCase):
+ def test_NoSpecialHeaders(self):
+
+ server = Session("www.example.com")
+ request = Copy(server, "/a", "http://www.example.com/b")
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertFalse("If-Match:" in hdrs)
+ self.assertTrue("Overwrite: F" in hdrs)
+ self.assertTrue("Destination: http://www.example.com/b" in hdrs)
+
+ def test_IfMatchHeader(self):
+
+ server = Session("www.example.com")
+ request = Copy(server, "/a", "http://www.example.com/b")
+ request.setData(etag="\"12345\"")
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertTrue("If-Match: \"12345\"" in hdrs)
+ self.assertTrue("Overwrite: F" in hdrs)
+ self.assertTrue("Destination: http://www.example.com/b" in hdrs)
+
+ def test_OverwriteHeader(self):
+
+ server = Session("www.example.com")
+ request = Copy(server, "/a", "http://www.example.com/b", overwrite=True)
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertFalse("If-Match:" in hdrs)
+ self.assertTrue("Overwrite: T" in hdrs)
+ self.assertTrue("Destination: http://www.example.com/b" in hdrs)
+
+class TestRequestBody(unittest.TestCase):
+ pass
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+ pass
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_delete.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_delete.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_delete.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,58 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.http.session import Session
+from protocol.webdav.delete import Delete
+
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = Delete(server, "/")
+ self.assertEqual(request.getMethod(), "DELETE")
+
+class TestRequestHeaders(unittest.TestCase):
+ def test_NoSpecialHeaders(self):
+
+ server = Session("www.example.com")
+ request = Delete(server, "/")
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertFalse("If-Match:" in hdrs)
+
+ def test_IfMatchHeader(self):
+
+ server = Session("www.example.com")
+ request = Delete(server, "/")
+ request.setData(etag="\"12345\"")
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertTrue("If-Match: \"12345\"" in hdrs)
+
+class TestRequestBody(unittest.TestCase):
+ pass
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+ pass
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_get.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_get.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_get.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,58 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.http.session import Session
+from protocol.webdav.get import Get
+
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = Get(server, "/")
+ self.assertEqual(request.getMethod(), "GET")
+
+class TestRequestHeaders(unittest.TestCase):
+ def test_NoSpecialHeaders(self):
+
+ server = Session("www.example.com")
+ request = Get(server, "/")
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertFalse("If-Match:" in hdrs)
+
+ def test_IfMatchHeader(self):
+
+ server = Session("www.example.com")
+ request = Get(server, "/")
+ request.setData(None, etag="\"12345\"")
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertTrue("If-Match: \"12345\"" in hdrs)
+
+class TestRequestBody(unittest.TestCase):
+ pass
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+ pass
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_head.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_head.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_head.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,58 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.http.session import Session
+from protocol.webdav.head import Head
+
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = Head(server, "/")
+ self.assertEqual(request.getMethod(), "HEAD")
+
+class TestRequestHeaders(unittest.TestCase):
+ def test_NoSpecialHeaders(self):
+
+ server = Session("www.example.com")
+ request = Head(server, "/")
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertFalse("If-Match:" in hdrs)
+
+ def test_IfMatchHeader(self):
+
+ server = Session("www.example.com")
+ request = Head(server, "/")
+ request.setData(None, etag="\"12345\"")
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertTrue("If-Match: \"12345\"" in hdrs)
+
+class TestRequestBody(unittest.TestCase):
+ pass
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+ pass
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_lock.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_lock.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_lock.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,119 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.http.session import Session
+from protocol.webdav.lock import Lock
+from protocol.webdav.definitions import headers
+from StringIO import StringIO
+
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = Lock(server, "/", headers.Depth0, Lock.eExclusive, "user at example.com", -1)
+ self.assertEqual(request.getMethod(), "LOCK")
+
+class TestRequestHeaders(unittest.TestCase):
+
+ def test_NoSpecialHeaders(self):
+
+ server = Session("www.example.com")
+ request = Lock(server, "/", headers.Depth0, Lock.eExclusive, "user at example.com", -1, exists=Lock.eResourceMayExist)
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertFalse("If-Match:" in hdrs)
+
+ def test_IfMatchHeader(self):
+
+ server = Session("www.example.com")
+ request = Lock(server, "/", headers.Depth0, Lock.eExclusive, "user at example.com", -1, exists=Lock.eResourceMustExist)
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertTrue("If-Match: *" in hdrs)
+
+ def test_IfNoneMatchHeader(self):
+
+ server = Session("www.example.com")
+ request = Lock(server, "/", headers.Depth0, Lock.eExclusive, "user at example.com", -1, exists=Lock.eResourceMustNotExist)
+ hdrs = request.generateRequestHeader()
+ self.assertTrue("If-None-Match: *" in hdrs)
+ self.assertFalse("If-Match:" in hdrs)
+
+ def test_Depth0Headers(self):
+
+ server = Session("www.example.com")
+ request = Lock(server, "/", headers.Depth0, Lock.eExclusive, "user at example.com", -1, exists=Lock.eResourceMustNotExist)
+ hdrs = request.generateRequestHeader()
+ self.assertTrue("Depth: 0" in hdrs)
+ self.assertFalse("Depth: 1" in hdrs)
+ self.assertFalse("Depth: infinity" in hdrs)
+
+ def test_Depth1Headers(self):
+
+ server = Session("www.example.com")
+ request = Lock(server, "/", headers.Depth1, Lock.eExclusive, "user at example.com", -1, exists=Lock.eResourceMustNotExist)
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("Depth: 0" in hdrs)
+ self.assertTrue("Depth: 1" in hdrs)
+ self.assertFalse("Depth: infinity" in hdrs)
+
+ def test_DepthInfinityHeaders(self):
+
+ server = Session("www.example.com")
+ request = Lock(server, "/", headers.DepthInfinity, Lock.eExclusive, "user at example.com", -1, exists=Lock.eResourceMustNotExist)
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("Depth: 0" in hdrs)
+ self.assertFalse("Depth: 1" in hdrs)
+ self.assertTrue("Depth: infinity" in hdrs)
+
+
+class TestRequestBody(unittest.TestCase):
+
+ def test_GenerateXML(self):
+
+ server = Session("www.example.com")
+ request = Lock(server, "/", headers.Depth0, Lock.eExclusive, "user at example.com", -1)
+ os = StringIO()
+ request.generateXML(os)
+ self.assertEqual(os.getvalue(), """<?xml version='1.0' encoding='utf-8'?>
+<ns0:lockinfo xmlns:ns0="DAV:">
+ <ns0:lockscope>
+ <ns0:exclusive />
+ </ns0:lockscope>
+ <ns0:locktype>
+ <ns0:write />
+ </ns0:locktype>
+ <ns0:owner>user at example.com</ns0:owner>
+</ns0:lockinfo>
+""".replace("\n", "\r\n")
+)
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+
+ def test_OneHeader(self):
+
+ server = Session("www.example.com")
+ request = Lock(server, "/", headers.Depth0, Lock.eExclusive, "user at example.com", -1)
+ request.getResponseHeaders().update({
+ "Lock-Token": ("<user at example.com>",),
+ })
+ self.assertEqual(request.getLockToken(), "user at example.com")
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_move.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_move.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_move.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,72 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.http.session import Session
+from protocol.webdav.move import Move
+
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = Move(server, "/a", "http://www.example.com/b")
+ self.assertEqual(request.getMethod(), "MOVE")
+
+class TestRequestHeaders(unittest.TestCase):
+ def test_NoSpecialHeaders(self):
+
+ server = Session("www.example.com")
+ request = Move(server, "/a", "http://www.example.com/b")
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertFalse("If-Match:" in hdrs)
+ self.assertTrue("Overwrite: F" in hdrs)
+ self.assertTrue("Destination: http://www.example.com/b" in hdrs)
+
+ def test_IfMatchHeader(self):
+
+ server = Session("www.example.com")
+ request = Move(server, "/a", "http://www.example.com/b")
+ request.setData(etag="\"12345\"")
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertTrue("If-Match: \"12345\"" in hdrs)
+ self.assertTrue("Overwrite: F" in hdrs)
+ self.assertTrue("Destination: http://www.example.com/b" in hdrs)
+
+ def test_OverwriteHeader(self):
+
+ server = Session("www.example.com")
+ request = Move(server, "/a", "http://www.example.com/b", overwrite=True)
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertFalse("If-Match:" in hdrs)
+ self.assertTrue("Overwrite: T" in hdrs)
+ self.assertTrue("Destination: http://www.example.com/b" in hdrs)
+
+class TestRequestBody(unittest.TestCase):
+ pass
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+ pass
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_options.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_options.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_options.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,68 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.http.session import Session
+from protocol.webdav.options import Options
+
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = Options(server, "/")
+ self.assertEqual(request.getMethod(), "OPTIONS")
+
+class TestRequestHeaders(unittest.TestCase):
+ pass
+
+class TestRequestBody(unittest.TestCase):
+ pass
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+
+ def test_OneHeader(self):
+
+ server = Session("www.example.com")
+ request = Options(server, "/")
+ request.getResponseHeaders().update({
+ "Allow": ("GET, PUT, OPTIONS, HEAD",),
+ })
+ self.assertEqual(set(request.getAllowed()), set(("GET", "PUT", "OPTIONS", "HEAD")))
+ self.assertTrue(request.isAllowed("GET"))
+ self.assertTrue(request.isAllowed("PUT"))
+ self.assertTrue(request.isAllowed("OPTIONS"))
+ self.assertTrue(request.isAllowed("HEAD"))
+
+ def test_MultipleHeader(self):
+
+ server = Session("www.example.com")
+ request = Options(server, "/")
+ request.getResponseHeaders().update({
+ "Allow": ("GET, PUT", "OPTIONS, HEAD",),
+ })
+ self.assertEqual(set(request.getAllowed()), set(("GET", "PUT", "OPTIONS", "HEAD")))
+ self.assertTrue(request.isAllowed("GET"))
+ self.assertTrue(request.isAllowed("PUT"))
+ self.assertTrue(request.isAllowed("OPTIONS"))
+ self.assertTrue(request.isAllowed("HEAD"))
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_principalmatch.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_principalmatch.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_principalmatch.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,84 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.session import Session
+from protocol.webdav.principalmatch import PrincipalMatch
+from StringIO import StringIO
+from protocol.webdav.definitions import davxml
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = PrincipalMatch(server, "/", ())
+ self.assertEqual(request.getMethod(), "REPORT")
+
+class TestRequestHeaders(unittest.TestCase):
+
+ def test_Depth0Headers(self):
+
+ server = Session("www.example.com")
+ request = PrincipalMatch(server, "/", ())
+ hdrs = request.generateRequestHeader()
+ self.assertTrue("Depth: 0" in hdrs)
+ self.assertFalse("Depth: 1" in hdrs)
+ self.assertFalse("Depth: infinity" in hdrs)
+
+class TestRequestBody(unittest.TestCase):
+
+ def test_GenerateXMLOneProperty(self):
+
+ server = Session("www.example.com")
+ request = PrincipalMatch(server, "/", (davxml.getetag,))
+ os = StringIO()
+ request.generateXML(os)
+ self.assertEqual(os.getvalue(), """<?xml version='1.0' encoding='utf-8'?>
+<ns0:principal-match xmlns:ns0="DAV:">
+ <ns0:self />
+ <ns0:prop>
+ <ns0:getetag />
+ </ns0:prop>
+</ns0:principal-match>
+""".replace("\n", "\r\n")
+)
+
+ def test_GenerateXMLMultipleProperties(self):
+
+ server = Session("www.example.com")
+ request = PrincipalMatch(server, "/", (davxml.getetag, davxml.displayname,))
+ os = StringIO()
+ request.generateXML(os)
+ self.assertEqual(os.getvalue(), """<?xml version='1.0' encoding='utf-8'?>
+<ns0:principal-match xmlns:ns0="DAV:">
+ <ns0:self />
+ <ns0:prop>
+ <ns0:getetag />
+ <ns0:displayname />
+ </ns0:prop>
+</ns0:principal-match>
+""".replace("\n", "\r\n")
+)
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+ pass
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_propfind.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_propfind.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_propfind.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,87 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.session import Session
+from protocol.webdav.propfind import PropFind
+from StringIO import StringIO
+from protocol.webdav.definitions import davxml
+from xml.etree.ElementTree import QName
+from protocol.webdav.definitions import headers
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = PropFind(server, "/", headers.Depth0, (davxml.getetag, QName("http://example.com/ns/", "taggy")))
+ self.assertEqual(request.getMethod(), "PROPFIND")
+
+class TestRequestHeaders(unittest.TestCase):
+
+ def test_Depth0Headers(self):
+
+ server = Session("www.example.com")
+ request = PropFind(server, "/", headers.Depth0, (davxml.getetag, QName("http://example.com/ns/", "taggy")))
+ hdrs = request.generateRequestHeader()
+ self.assertTrue("Depth: 0" in hdrs)
+ self.assertFalse("Depth: 1" in hdrs)
+ self.assertFalse("Depth: infinity" in hdrs)
+
+ def test_Depth1Headers(self):
+
+ server = Session("www.example.com")
+ request = PropFind(server, "/", headers.Depth1, (davxml.getetag, QName("http://example.com/ns/", "taggy")))
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("Depth: 0" in hdrs)
+ self.assertTrue("Depth: 1" in hdrs)
+ self.assertFalse("Depth: infinity" in hdrs)
+
+ def test_DepthInfinityHeaders(self):
+
+ server = Session("www.example.com")
+ request = PropFind(server, "/", headers.DepthInfinity, (davxml.getetag, QName("http://example.com/ns/", "taggy")))
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("Depth: 0" in hdrs)
+ self.assertFalse("Depth: 1" in hdrs)
+ self.assertTrue("Depth: infinity" in hdrs)
+
+class TestRequestBody(unittest.TestCase):
+
+ def test_GenerateXML(self):
+
+ server = Session("www.example.com")
+ request = PropFind(server, "/", headers.Depth0, (davxml.getetag, QName("http://example.com/ns/", "taggy")))
+ os = StringIO()
+ request.generateXML(os)
+ self.assertEqual(os.getvalue(), """<?xml version='1.0' encoding='utf-8'?>
+<ns0:propfind xmlns:ns0="DAV:">
+ <ns0:prop>
+ <ns0:getetag />
+ <ns1:taggy xmlns:ns1="http://example.com/ns/" />
+ </ns0:prop>
+</ns0:propfind>
+""".replace("\n", "\r\n")
+)
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+ pass
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_propfindparser.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_propfindparser.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_propfindparser.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,121 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.propfindparser import PropFindParser
+from xml.etree.ElementTree import XML
+from protocol.webdav.definitions import davxml
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def parseXML(self, x):
+
+ x = x.replace("\n", "\r\n")
+
+ # Parse the XML data
+ p = PropFindParser()
+ p.parse(XML(x))
+ return p
+
+ def checkResource(self, parser, resource, properties):
+ result = parser.getResults().get(resource, None)
+ self.assertTrue(result is not None)
+ for prop, value in properties:
+ self.assertEqual(result.getTextProperties().get(prop, None), value)
+
+ def test_SinglePropSingleResource(self):
+
+ parser = self.parseXML("""<?xml version='1.0' encoding='utf-8'?>
+<ns0:multistatus xmlns:ns0="DAV:">
+ <ns0:response>
+ <ns0:href>/principals/users/a</ns0:href>
+ <ns0:propstat>
+ <ns0:prop>
+ <ns0:getetag>12345</ns0:getetag>
+ </ns0:prop>
+ <ns0:status>HTTP/1.1 200 OK</ns0:status>
+ </ns0:propstat>
+ </ns0:response>
+</ns0:multistatus>
+""")
+
+ self.checkResource(parser, "/principals/users/a", (
+ (davxml.getetag, "12345",),
+ ))
+
+ def test_MultiplePropsSingleResource(self):
+
+ parser = self.parseXML("""<?xml version='1.0' encoding='utf-8'?>
+<ns0:multistatus xmlns:ns0="DAV:">
+ <ns0:response>
+ <ns0:href>/principals/users/a</ns0:href>
+ <ns0:propstat>
+ <ns0:prop>
+ <ns0:displayname>Name</ns0:displayname>
+ <ns0:getetag>12345</ns0:getetag>
+ </ns0:prop>
+ <ns0:status>HTTP/1.1 200 OK</ns0:status>
+ </ns0:propstat>
+ </ns0:response>
+</ns0:multistatus>
+""")
+
+ result = parser.getResults().get("/principals/users/a", None)
+ self.assertTrue(result is not None)
+ self.assertEqual(result.getTextProperties().get(davxml.getetag, None), "12345")
+ self.assertEqual(result.getTextProperties().get(davxml.displayname, None), "Name")
+
+ self.checkResource(parser, "/principals/users/a", (
+ (davxml.getetag, "12345",),
+ (davxml.displayname, "Name",),
+ ))
+
+ def test_MultiplePropsSingleMultipleResources(self):
+
+ parser = self.parseXML("""<?xml version='1.0' encoding='utf-8'?>
+<ns0:multistatus xmlns:ns0="DAV:">
+ <ns0:response>
+ <ns0:href>/principals/users/a</ns0:href>
+ <ns0:propstat>
+ <ns0:prop>
+ <ns0:displayname>Name1</ns0:displayname>
+ <ns0:getetag>1</ns0:getetag>
+ </ns0:prop>
+ <ns0:status>HTTP/1.1 200 OK</ns0:status>
+ </ns0:propstat>
+ </ns0:response>
+ <ns0:response>
+ <ns0:href>/principals/users/b</ns0:href>
+ <ns0:propstat>
+ <ns0:prop>
+ <ns0:displayname>Name2</ns0:displayname>
+ <ns0:getetag>2</ns0:getetag>
+ </ns0:prop>
+ <ns0:status>HTTP/1.1 200 OK</ns0:status>
+ </ns0:propstat>
+ </ns0:response>
+</ns0:multistatus>
+""")
+
+ self.checkResource(parser, "/principals/users/a", (
+ (davxml.getetag, "1",),
+ (davxml.displayname, "Name1",),
+ ))
+
+ self.checkResource(parser, "/principals/users/b", (
+ (davxml.getetag, "2",),
+ (davxml.displayname, "Name2",),
+ ))
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_propnames.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_propnames.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_propnames.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,82 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.session import Session
+from protocol.webdav.propnames import PropNames
+from StringIO import StringIO
+from protocol.webdav.definitions import headers
+import unittest
+
+class TestPropNames(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = PropNames(server, "/", headers.Depth0)
+ self.assertEqual(request.getMethod(), "PROPFIND")
+
+class TestRequestHeaders(unittest.TestCase):
+
+ def test_Depth0Headers(self):
+
+ server = Session("www.example.com")
+ request = PropNames(server, "/", headers.Depth0)
+ hdrs = request.generateRequestHeader()
+ self.assertTrue("Depth: 0" in hdrs)
+ self.assertFalse("Depth: 1" in hdrs)
+ self.assertFalse("Depth: infinity" in hdrs)
+
+ def test_Depth1Headers(self):
+
+ server = Session("www.example.com")
+ request = PropNames(server, "/", headers.Depth1)
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("Depth: 0" in hdrs)
+ self.assertTrue("Depth: 1" in hdrs)
+ self.assertFalse("Depth: infinity" in hdrs)
+
+ def test_DepthInfinityHeaders(self):
+
+ server = Session("www.example.com")
+ request = PropNames(server, "/", headers.DepthInfinity)
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("Depth: 0" in hdrs)
+ self.assertFalse("Depth: 1" in hdrs)
+ self.assertTrue("Depth: infinity" in hdrs)
+
+class TestRequestBody(unittest.TestCase):
+
+ def test_GenerateXML(self):
+
+ server = Session("www.example.com")
+ request = PropNames(server, "/", headers.Depth0)
+ os = StringIO()
+ request.generateXML(os)
+ self.assertEqual(os.getvalue(), """<?xml version='1.0' encoding='utf-8'?>
+<ns0:propfind xmlns:ns0="DAV:">
+ <ns0:propname />
+</ns0:propfind>
+""".replace("\n", "\r\n")
+)
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+ pass
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_put.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_put.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_put.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,75 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.http.session import Session
+from protocol.webdav.put import Put
+
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = Put(server, "/")
+ self.assertEqual(request.getMethod(), "PUT")
+
+class TestRequestHeaders(unittest.TestCase):
+
+ def test_NoSpecialHeaders(self):
+
+ server = Session("www.example.com")
+ request = Put(server, "/")
+ request.setData(None, None)
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertFalse("If-Match:" in hdrs)
+
+ def test_IfMatchHeader(self):
+
+ server = Session("www.example.com")
+ request = Put(server, "/")
+ request.setData(None, None, etag="\"12345\"")
+ hdrs = request.generateRequestHeader()
+ self.assertFalse("If-None-Match:" in hdrs)
+ self.assertTrue("If-Match: \"12345\"" in hdrs)
+
+ def test_IfNoneMatchHeader(self):
+
+ server = Session("www.example.com")
+ request = Put(server, "/")
+ request.setData(None, None, new_item=True)
+ hdrs = request.generateRequestHeader()
+ self.assertTrue("If-None-Match: *" in hdrs)
+ self.assertFalse("If-Match:" in hdrs)
+
+ def test_Bad(self):
+
+ server = Session("www.example.com")
+ request = Put(server, "/")
+ self.assertRaises(AssertionError, request.setData, None, None, **{"etag": "\"12345\"", "new_item": True})
+
+class TestRequestBody(unittest.TestCase):
+ pass
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+ pass
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_report.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_report.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_report.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,43 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.http.session import Session
+from protocol.webdav.report import Report
+
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = Report(server, "/")
+ self.assertEqual(request.getMethod(), "REPORT")
+
+class TestRequestHeaders(unittest.TestCase):
+ pass
+
+class TestRequestBody(unittest.TestCase):
+ pass
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+ pass
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_template.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_template.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_template.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,35 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+import unittest
+
+class TestRequest(unittest.TestCase):
+ pass
+
+class TestRequestHeaders(unittest.TestCase):
+ pass
+
+class TestRequestBody(unittest.TestCase):
+ pass
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+ pass
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_unlock.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_unlock.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/tests/test_unlock.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,49 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.http.session import Session
+from protocol.webdav.unlock import Unlock
+
+import unittest
+
+class TestRequest(unittest.TestCase):
+
+ def test_Method(self):
+
+ server = Session("www.example.com")
+ request = Unlock(server, "/", "locked-up-in-chains")
+ self.assertEqual(request.getMethod(), "UNLOCK")
+
+class TestRequestHeaders(unittest.TestCase):
+
+ def test_LockTokenHeaders(self):
+
+ server = Session("www.example.com")
+ request = Unlock(server, "/", "locked-up-in-chains")
+ hdrs = request.generateRequestHeader()
+ self.assertTrue("Lock-Token: <locked-up-in-chains>" in hdrs)
+
+class TestRequestBody(unittest.TestCase):
+ pass
+
+class TestResponse(unittest.TestCase):
+ pass
+
+class TestResponseHeaders(unittest.TestCase):
+ pass
+
+class TestResponseBody(unittest.TestCase):
+ pass
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/unlock.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/unlock.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/unlock.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,34 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.webdav.requestresponse import RequestResponse
+from protocol.webdav.definitions import methods
+from protocol.webdav.definitions import headers
+
+class Unlock(RequestResponse):
+
+ def __init__(self, session, url, lock_token):
+
+ super(Unlock, self).__init__(session, methods.UNLOCK, url)
+
+ self.lock_token = lock_token
+
+ def addHeaders(self, hdrs):
+ # Do default
+ super(Unlock, self).addHeaders(hdrs)
+
+ # Add lock-token header
+ hdrs.append((headers.LockToken, "<%s>" % (self.lock_token,)))
Added: CalDAVClientLibrary/trunk/src/protocol/webdav/xmlresponseparser.py
===================================================================
--- CalDAVClientLibrary/trunk/src/protocol/webdav/xmlresponseparser.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/protocol/webdav/xmlresponseparser.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,31 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from xml.etree.ElementTree import XML
+
+class XMLResponseParser(object):
+
+ def parseData(self, data):
+ # XML parse the data
+ self.parse(XML(data))
+
+ def parseFile(self, fpath):
+ fp = open(fpath, "r")
+ self.parse(XML(fp.read()))
+ fp.close()
+
+ def parse(self, root_node):
+ raise NotImplementedError
Added: CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.nib/classes.nib
===================================================================
--- CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.nib/classes.nib (rev 0)
+++ CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.nib/classes.nib 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBClasses</key>
+ <array>
+ <dict>
+ <key>CLASS</key>
+ <string>FirstResponder</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>SUPERCLASS</key>
+ <string>NSObject</string>
+ </dict>
+ <dict>
+ <key>CLASS</key>
+ <string>NSTableView</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>SUPERCLASS</key>
+ <string>NSControl</string>
+ </dict>
+ <dict>
+ <key>ACTIONS</key>
+ <dict>
+ <key>browserAction</key>
+ <string>id</string>
+ <key>changeBrowserView</key>
+ <string>id</string>
+ <key>changeDataView</key>
+ <string>id</string>
+ <key>resetServer</key>
+ <string>id</string>
+ <key>startupCancelAction</key>
+ <string>id</string>
+ <key>startupOKAction</key>
+ <string>id</string>
+ <key>tableAction</key>
+ <string>id</string>
+ </dict>
+ <key>CLASS</key>
+ <string>WebDAVBrowserDelegate</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>OUTLETS</key>
+ <dict>
+ <key>browser</key>
+ <string>id</string>
+ <key>columnView</key>
+ <string>id</string>
+ <key>dataView</key>
+ <string>id</string>
+ <key>list</key>
+ <string>id</string>
+ <key>listView</key>
+ <string>id</string>
+ <key>mainSplitterView</key>
+ <string>id</string>
+ <key>passwordText</key>
+ <string>id</string>
+ <key>pathLabel</key>
+ <string>id</string>
+ <key>progress</key>
+ <string>id</string>
+ <key>propertiesView</key>
+ <string>id</string>
+ <key>serverText</key>
+ <string>id</string>
+ <key>startupSheet</key>
+ <string>id</string>
+ <key>table</key>
+ <string>id</string>
+ <key>text</key>
+ <string>id</string>
+ <key>toolbarBrowserViewButton</key>
+ <string>id</string>
+ <key>toolbarDataViewButton</key>
+ <string>id</string>
+ <key>userText</key>
+ <string>id</string>
+ <key>webView</key>
+ <string>id</string>
+ <key>window</key>
+ <string>id</string>
+ </dict>
+ </dict>
+ </array>
+ <key>IBVersion</key>
+ <string>1</string>
+</dict>
+</plist>
Added: CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.nib/info.nib
===================================================================
--- CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.nib/info.nib (rev 0)
+++ CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.nib/info.nib 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBFramework Version</key>
+ <string>629</string>
+ <key>IBOldestOS</key>
+ <integer>5</integer>
+ <key>IBOpenObjects</key>
+ <array/>
+ <key>IBSystem Version</key>
+ <string>9B18</string>
+ <key>targetFramework</key>
+ <string>IBCocoaFramework</string>
+</dict>
+</plist>
Added: CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.nib/keyedobjects.nib
===================================================================
(Binary files differ)
Property changes on: CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.nib/keyedobjects.nib
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.py
===================================================================
--- CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/ui/WebDAVBrowser.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,574 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+"""
+Code based on PyObjC examples included with Apple Developer tools.
+"""
+
+import Foundation, AppKit, WebKit #@UnusedImport
+
+from AppKit import * #@UnusedWildImport
+from AppKit import NSApplication #@UnresolvedImport
+from AppKit import NSImage #@UnresolvedImport
+from AppKit import NSMenuItem #@UnresolvedImport
+from AppKit import NSObject #@UnresolvedImport
+from AppKit import NSPlainFileType #@UnresolvedImport
+from AppKit import NSSize #@UnresolvedImport
+from AppKit import NSToolbar #@UnresolvedImport
+from AppKit import NSToolbarCustomizeToolbarItemIdentifier #@UnresolvedImport
+from AppKit import NSToolbarFlexibleSpaceItemIdentifier #@UnresolvedImport
+from AppKit import NSToolbarItem #@UnresolvedImport
+from AppKit import NSToolbarPrintItemIdentifier #@UnresolvedImport
+from AppKit import NSToolbarSeparatorItemIdentifier #@UnresolvedImport
+from AppKit import NSToolbarSpaceItemIdentifier #@UnresolvedImport
+from AppKit import NSURL #@UnresolvedImport
+from AppKit import NSUserDefaults #@UnresolvedImport
+from AppKit import NSWorkspace #@UnresolvedImport
+
+from Foundation import * #@UnusedWildImport
+
+from PyObjCTools import NibClassBuilder, AppHelper
+
+from objc import IBOutlet #@UnusedImport
+from objc import YES, NO
+from objc import selector #@UnusedImport
+
+from protocol.utils import xmlhelpers
+from ui.session import Session
+from xml.etree.ElementTree import _ElementInterface
+import types
+
+NibClassBuilder.extractClasses("WebDAVBrowser")
+
+kServerToolbarItemIdentifier = "WDB: Server Toolbar Identifier"
+kRefreshToolbarItemIdentifier = "WDB: Refresh Toolbar Identifier"
+kBrowserViewToolbarItemIdentifier = "WDB: Browser View Toolbar Identifier"
+kDataViewToolbarItemIdentifier = "WDB: Data View Toolbar Identifier"
+
+def addToolbarItem(aController, anIdentifier, aLabel, aPaletteLabel,
+ aToolTip, aTarget, anAction, anItemContent, aMenu):
+ """
+ Add a toolbar button of some kind.
+ """
+ toolbarItem = NSToolbarItem.alloc().initWithItemIdentifier_(anIdentifier)
+
+ toolbarItem.setLabel_(aLabel)
+ toolbarItem.setPaletteLabel_(aPaletteLabel)
+ toolbarItem.setToolTip_(aToolTip)
+ toolbarItem.setTarget_(aTarget)
+ if anAction:
+ toolbarItem.setAction_(anAction)
+
+ if type(anItemContent) == NSImage:
+ toolbarItem.setImage_(anItemContent)
+ else:
+ toolbarItem.setView_(anItemContent)
+ bounds = anItemContent.bounds()
+ minSize = (bounds[1][0], bounds[1][1])
+ maxSize = (bounds[1][0], bounds[1][1])
+ toolbarItem.setMinSize_( minSize )
+ toolbarItem.setMaxSize_( maxSize )
+
+ if aMenu:
+ menuItem = NSMenuItem.alloc().init()
+ menuItem.setSubmenu_(aMenu)
+ menuItem.setTitle_( aMenu.title() )
+ toolbarItem.setMenuFormRepresentation_(menuItem)
+
+ aController._toolbarItems[anIdentifier] = toolbarItem
+
+WRAPPED={}
+class Wrapper(NSObject):
+ """
+ NSOutlineView doesn't retain values, which means we cannot use normal
+ python values as values in an outline view.
+ """
+ def init_(self, value):
+ self.value = value
+ return self
+
+ def __str__(self):
+ return '<Wrapper for %s>' % self.value
+
+ def description(self):
+ return str(self)
+
+def wrap_object(obj):
+ if WRAPPED.has_key(obj):
+ return WRAPPED[obj]
+ else:
+ WRAPPED[obj] = Wrapper.alloc().init_(obj)
+ return WRAPPED[obj]
+
+def unwrap_object(obj):
+ if obj is None:
+ return obj
+ return obj.value
+
+class WebDAVBrowserDelegate(NibClassBuilder.AutoBaseClass):
+ """
+ Class defined in NIB file. This acts as the delegate and responder
+ for the various NSViews and toolbar items. It basically our controller.
+ """
+
+ BROWSERVIEW_COLUMNS = 0
+ BROWSERVIEW_LIST = 1
+
+ DATAVIEW_PROPERTIES = 0
+ DATAVIEW_DATA = 1
+ #DATAVIEW_DELEGATES = 2
+ #DATAVIEW_ACLS = 3
+
+ selectedDetails = None
+
+ def awakeFromNib(self):
+ # Initialise our session and selected state parameters
+ self.session = None
+ self.columns = [[]]
+ self.selectedResource = None
+ self.selectedDetails = None
+ self.selectedData = None
+
+ # Cache some useful icons
+ self.fileImage = NSWorkspace.sharedWorkspace().iconForFileType_(NSPlainFileType)
+ self.fileImage.setSize_(NSSize(16, 16))
+ self.directoryImage = NSWorkspace.sharedWorkspace().iconForFile_("/usr/")
+ self.directoryImage.setSize_(NSSize(16, 16))
+
+ # Initialise the toolbar
+ self._toolbarItems = {}
+ self._toolbarDefaultItemIdentifiers = []
+ self._toolbarAllowedItemIdentifiers = []
+ self.createToolbar()
+
+ # Set up browser view
+ container = self.mainSplitterView.subviews()[0]
+ container.addSubview_(self.columnView)
+ container.addSubview_(self.listView)
+ self.currentBrowserView = None
+ self.setBrowserView(self.BROWSERVIEW_COLUMNS)
+ self.browser.setMaxVisibleColumns_(7)
+ self.browser.setMinColumnWidth_(150)
+
+ # Set up data view
+ container = self.mainSplitterView.subviews()[1]
+ container.addSubview_(self.propertiesView)
+ container.addSubview_(self.dataView)
+ self.currentDataView = None
+ self.setDataView(self.DATAVIEW_PROPERTIES)
+ self.text.setString_("")
+
+ self.pathLabel.setStringValue_("No server specified")
+
+ # Get preferences
+ lastServer = NSUserDefaults.standardUserDefaults().stringForKey_("LastServer")
+ if lastServer and len(lastServer):
+ self.serverText.setStringValue_(lastServer)
+ lastUser = NSUserDefaults.standardUserDefaults().stringForKey_("LastUser")
+ if lastUser and len(lastUser):
+ self.userText.setStringValue_(lastUser)
+
+ def createToolbar(self):
+ """
+ Create the toolbar for our app.
+ """
+ toolbar = NSToolbar.alloc().initWithIdentifier_("Toolbar")
+ toolbar.setDelegate_(self)
+ toolbar.setAllowsUserCustomization_(YES)
+ toolbar.setAutosavesConfiguration_(YES)
+
+ self.createToolbarItems()
+
+ self.window.setToolbar_(toolbar)
+
+ def createToolbarItems(self):
+ """
+ Create the toolbar item and define the default and allowed set.
+ """
+ addToolbarItem(self, kServerToolbarItemIdentifier,
+ "Server", "Server", "Reset Server", self,
+ "resetServer:", NSImage.imageNamed_("NSNetwork"), None,)
+
+ addToolbarItem(self, kRefreshToolbarItemIdentifier,
+ "Refresh", "Refresh", "Refresh Display", self,
+ "refreshData:", NSImage.imageNamed_("NSRefresh"), None,)
+
+ addToolbarItem(self, kBrowserViewToolbarItemIdentifier,
+ "Browser", "Browser", "Browser View", self,
+ "changeBrowserView:", self.toolbarBrowserViewButton, None,)
+
+ addToolbarItem(self, kDataViewToolbarItemIdentifier,
+ "View", "View", "Data View", self,
+ "changeDataView:", self.toolbarDataViewButton, None,)
+
+ self._toolbarDefaultItemIdentifiers = [
+ kServerToolbarItemIdentifier,
+ kBrowserViewToolbarItemIdentifier,
+ kDataViewToolbarItemIdentifier,
+ NSToolbarFlexibleSpaceItemIdentifier,
+ kRefreshToolbarItemIdentifier,
+ ]
+
+ self._toolbarAllowedItemIdentifiers = [
+ kServerToolbarItemIdentifier,
+ kBrowserViewToolbarItemIdentifier,
+ kDataViewToolbarItemIdentifier,
+ kRefreshToolbarItemIdentifier,
+ NSToolbarSeparatorItemIdentifier,
+ NSToolbarSpaceItemIdentifier,
+ NSToolbarFlexibleSpaceItemIdentifier,
+ NSToolbarPrintItemIdentifier,
+ NSToolbarCustomizeToolbarItemIdentifier,
+ ]
+
+ def toolbarDefaultItemIdentifiers_(self, anIdentifier):
+ """
+ Return the default set of toolbar items.
+ """
+ return self._toolbarDefaultItemIdentifiers
+
+ def toolbarAllowedItemIdentifiers_(self, anIdentifier):
+ """
+ Return the allowed set of toolbar items.
+ """
+ return self._toolbarAllowedItemIdentifiers
+
+ def toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar_(self,
+ toolbar,
+ itemIdentifier, flag):
+ """
+ Delegate method fired when the toolbar is about to insert an
+ item into the toolbar. Item is identified by itemIdentifier.
+
+ Effectively makes a copy of the cached reference instance of
+ the toolbar item identified by itemIdentifier.
+ """
+ newItem = NSToolbarItem.alloc().initWithItemIdentifier_(itemIdentifier)
+ item = self._toolbarItems[itemIdentifier]
+
+ newItem.setLabel_( item.label() )
+ newItem.setPaletteLabel_( item.paletteLabel() )
+ if item.view():
+ newItem.setView_( item.view() )
+ else:
+ newItem.setImage_( item.image() )
+
+ newItem.setToolTip_( item.toolTip() )
+ newItem.setTarget_( item.target() )
+ newItem.setAction_( item.action() )
+ newItem.setMenuFormRepresentation_( item.menuFormRepresentation() )
+
+ if newItem.view():
+ newItem.setMinSize_( item.minSize() )
+ newItem.setMaxSize_( item.maxSize() )
+
+ return newItem
+
+ def setBrowserView(self, view):
+ """
+ Change the browser view pane to the specified list type.
+ """
+ newView = {
+ self.BROWSERVIEW_COLUMNS: self.columnView,
+ self.BROWSERVIEW_LIST : self.listView,
+ }.get(view, None)
+ if self.currentBrowserView != newView:
+ if self.currentBrowserView:
+ self.currentBrowserView.setHidden_(YES)
+ self.currentBrowserView = newView
+ if self.currentBrowserView:
+ self.currentBrowserView.setHidden_(NO)
+ self.browserview = view
+ self.refreshView()
+
+ def changeBrowserView_(self, sender):
+ """
+ User clicked a browser toolbar button.
+ """
+ self.setBrowserView(sender.selectedSegment())
+
+ def setDataView(self, view):
+ """
+ Change the data view pane to the specified type.
+ """
+ newView = {
+ self.DATAVIEW_PROPERTIES: self.propertiesView,
+ self.DATAVIEW_DATA : self.dataView,
+ }.get(view, None)
+ if self.currentDataView != newView:
+ if self.currentDataView:
+ self.currentDataView.setHidden_(YES)
+ self.currentDataView = newView
+ if self.currentDataView:
+ self.currentDataView.setHidden_(NO)
+ self.dataview = view
+ self.refreshView()
+
+ def changeDataView_(self, sender):
+ """
+ User clicked a view toolbar button.
+ """
+ self.setDataView(sender.selectedSegment())
+
+ def resetServer_(self, sender):
+ """
+ Display the sheet asking for server details.
+ """
+ NSApplication.sharedApplication().beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
+ self.startupSheet,
+ self.window,
+ None, None, 0
+ )
+
+ def refreshData_(self, sender):
+ """
+ Force a refresh of the data for the current selected resource.
+ """
+ if self.selectedResource:
+ self.selectedResource.clear()
+
+ self.progress.startAnimation_(self)
+ resources = self.selectedResource.listChildren()
+ self.columns[-1] = resources
+ self.progress.stopAnimation_(self)
+
+ self.refreshView()
+
+ def refreshView(self):
+ """
+ Refresh the actual data view for the selected resource.
+ """
+ if self.selectedResource:
+ self.progress.startAnimation_(self)
+ if self.dataview == self.DATAVIEW_PROPERTIES:
+ self.selectedDetails = self.selectedResource.getAllDetails()
+ self.table.reloadData()
+ self.table.deselectAll_(self)
+ self.text.setString_("")
+ elif self.dataview == self.DATAVIEW_DATA:
+ self.selectedData = self.selectedResource.getDataAsHTML()
+ url = NSURL.alloc().initWithString_(self.serverText.stringValue())
+ self.webView.mainFrame().loadHTMLString_baseURL_(self.selectedData, url)
+ self.progress.stopAnimation_(self)
+
+ def startupOKAction_(self, btn):
+ """
+ User clicked OK in the server setup sheet.
+ """
+
+ # Create the actual session.
+ server = self.serverText.stringValue()
+ user = self.userText.stringValue()
+ pswd = self.passwordText.stringValue()
+ self.session = Session(server, user, pswd, logging=False)
+ self.window.setTitle_(self.serverText.stringValue())
+ self.pathLabel.setStringValue_("/")
+ NSUserDefaults.standardUserDefaults().setObject_forKey_(server, "LastServer")
+ NSUserDefaults.standardUserDefaults().setObject_forKey_(user, "LastUser")
+
+ # List the root resource.
+ self.progress.startAnimation_(self)
+ resources = self.session.getRoot().listChildren()
+ self.progress.stopAnimation_(self)
+ self.columns = [resources]
+
+ # Done with the sheet.
+ self.startupSheet.close()
+ NSApplication.sharedApplication().endSheet_(self.startupSheet)
+
+ # Force reload of browser pane views.
+ self.browser.loadColumnZero()
+ self.list.reloadItem_(None)
+
+ def startupCancelAction_(self, btn):
+ """
+ User clicked the cancel button in the server sheet.
+ """
+ self.startupSheet.close()
+ NSApplication.sharedApplication().endSheet_(self.startupSheet)
+
+ def browserAction_(self, browser):
+ """
+ Something changed in the column browser.
+ """
+
+ # Update current path.
+ self.pathLabel.setStringValue_(browser.path())
+
+ # Get new selected resource and refresh the data view.
+ self.selectedResource = None
+ self.selectedDetails = None
+ col = len(self.columns)
+ row = -1
+ while row == -1:
+ col -= 1
+ if col < 0:
+ break
+ row = self.browser.selectedRowInColumn_(col)
+ if row >= 0:
+ self.selectedResource = self.columns[col][row]
+
+ self.refreshView()
+
+ def browser_willDisplayCell_atRow_column_(self, browser, cell, row, col):
+ """
+ Delegate method to set the actual stuff displayed in a column view row.
+ """
+ isLeaf = not self.columns[col][row].isCollection()
+ cell.setLeaf_(isLeaf)
+ cell.setStringValue_(self.columns[col][row].getName())
+ cell.setImage_(self.fileImage if isLeaf else self.directoryImage)
+
+ def browser_numberOfRowsInColumn_(self, browser, col):
+ """
+ Delegate method that returns the number of rows in a column view column.
+ """
+ if col == 0:
+ return len(self.columns[0])
+ del self.columns[col:]
+ resource = self.columns[col - 1][browser.selectedRowInColumn_(col - 1)]
+ self.progress.startAnimation_(self)
+ resources = resource.listChildren()
+ self.progress.stopAnimation_(self)
+ self.columns.append(resources)
+ return len(resources)
+
+ def outlineViewSelectionDidChange_(self, notification):
+ """
+ Delegate method called when the selection in the outline view changes.
+ """
+
+ # Get the new selected resource and refresh the data view.
+ row = self.list.selectedRow()
+ if row == -1:
+ self.selectedResource = None
+ self.pathLabel.setStringValue_("Nothing Selected")
+ else:
+ self.selectedResource = unwrap_object(self.list.itemAtRow_(row))
+ self.pathLabel.setStringValue_(self.selectedResource.getPath())
+
+ self.refreshView()
+
+ def outlineView_numberOfChildrenOfItem_(self, outlineView, item):
+ """
+ Delegate method to return the number of children of an item in the outline view.
+ """
+ if self.session is None:
+ return 0
+ if item is None:
+ resource = self.session.getRoot()
+ else:
+ resource = unwrap_object(item)
+ self.progress.startAnimation_(self)
+ resources = resource.listChildren()
+ self.progress.stopAnimation_(self)
+ return len(resources)
+
+ def outlineView_isItemExpandable_(self, outlineView, item):
+ """
+ Delegate method to return the whether an item in the outline view is expandable.
+ """
+ if item is None:
+ return YES
+ else:
+ resource = unwrap_object(item)
+ return YES if resource.isCollection() else NO
+
+ def outlineView_child_ofItem_(self, outlineView, index, item):
+ """
+ Delegate method to return the item associated with a row in the outline view.
+ """
+ if item is None:
+ resource = self.session.getRoot()
+ else:
+ resource = unwrap_object(item)
+ self.progress.startAnimation_(self)
+ resources = resource.listChildren()
+ self.progress.stopAnimation_(self)
+ return wrap_object(resources[index])
+
+ def outlineView_objectValueForTableColumn_byItem_(self, outlineView, tableColumn, item):
+ """
+ Delegate method to return the data displayed in the outline view.
+ """
+ if item is None:
+ resource = self.session.getRoot()
+ else:
+ resource = unwrap_object(item)
+ return {
+ "Name" : resource.getName(),
+ "Size" : resource.getSize(),
+ "Modified": resource.getLastMod(),
+ }[tableColumn.identifier()]
+
+ def tableAction_(self, table):
+ """
+ Called when the selection in the properties list changes.
+ """
+
+ # Get the selected property and display its value.
+ row = self.table.selectedRow()
+ if row >= 0:
+ value = self.selectedDetails[row][1]
+ if type(value) in (types.ListType, types.TupleType,):
+ if len(value) == 1:
+ text = self.propValueToText(value[0])
+ else:
+ sorted = [self.propValueToText(v) for v in value]
+ sorted.sort()
+ text = "\r".join(sorted)
+ else:
+ text = self.propValueToText(value)
+ else:
+ text = ""
+ self.text.setString_(text)
+
+ def numberOfRowsInTableView_(self, tableView):
+ """
+ Delegate method to return the number of rows in the list.
+ @param tableView:
+ @type tableView:
+ """
+ if self.selectedDetails is None:
+ return 0
+ return len(self.selectedDetails)
+
+ def tableView_objectValueForTableColumn_row_(self, tableView, col, row):
+ """
+ Delegate method to return the text for a list cell.
+ """
+ return str(self.selectedDetails[row][0])
+
+ def tableView_shouldEditTableColumn_row_(self, tableView, col, row):
+ """
+ Delegate method to indicate whether a cell can be edited.
+ """
+ return 0
+
+ def propValueToText(self, value):
+ """
+ Do a sensible print of a property value taking type into account.
+ """
+ if type(value) in (types.StringType, types.UnicodeType, types.IntType):
+ text = str(value)
+ elif isinstance(value, _ElementInterface):
+ text = xmlhelpers.elementToString(value).replace("\n", "")
+ else:
+ text = str(value)
+ return text
+
+if __name__ == "__main__":
+ AppHelper.runEventLoop()
Added: CalDAVClientLibrary/trunk/src/ui/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/src/ui/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/ui/__init__.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2007-2008 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.
+##
Added: CalDAVClientLibrary/trunk/src/ui/resource.py
===================================================================
--- CalDAVClientLibrary/trunk/src/ui/resource.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/ui/resource.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,134 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from protocol.url import URL
+from protocol.webdav.definitions import davxml
+import os
+
+class Resource(object):
+ """
+ Maintains data for a WebDAV resource, including list of properties,
+ child resources and actual data.
+ """
+
+ def __init__(self, session, path, lastmod="", size="", type=""):
+
+ self.session = session
+ self.path = path.rstrip("/")
+ self.iscollection = path.endswith("/")
+ self.lastmod = lastmod
+ self.size = size
+ self.type = type
+ self.children = None
+ self.details = None
+ self.data = None
+
+ def getPath(self):
+ return self.path
+
+ def getName(self):
+ return os.path.basename(self.path)
+
+ def getLastMod(self):
+ return self.lastmod
+
+ def getSize(self):
+ return self.size
+
+ def getType(self):
+ return self.type
+
+ def isCollection(self):
+ return self.iscollection
+
+ def findPath(self, path=None, elements=None):
+ if path:
+ elements = path.lstrip("/").rstrip("/").split("/")
+ if self.listChildren():
+ for child in self.children:
+ if child.getName() == elements[0]:
+ elements = elements[1:]
+ if elements:
+ return child.findPath(elements=elements)
+ else:
+ return child
+ return None
+
+ def findChild(self, name):
+ if self.children:
+ for child in self.children:
+ if child.getName() == name:
+ return child
+ return None
+
+ def listChildren(self):
+ if self.children is None:
+ resource = URL(url=self.path+"/")
+ props = (
+ davxml.resourcetype,
+ davxml.getlastmodified,
+ davxml.getcontentlength,
+ davxml.getcontenttype,
+ )
+ results = self.session.account.session.getPropertiesOnHierarchy(resource, props)
+ items = results.keys()
+ items.sort()
+ self.children = [Resource(
+ self.session,
+ rurl,
+ lastmod=results[rurl].get(davxml.getlastmodified, ""),
+ size=results[rurl].get(davxml.getcontentlength, ""),
+ type=results[rurl].get(davxml.getcontenttype, ""),
+ ) for rurl in items if rurl != self.path + "/"]
+ return self.children
+
+ def getDetails(self):
+ resource = URL(url=self.path+"/")
+ props = (davxml.resourcetype,)
+ props += (davxml.getcontentlength, davxml.getlastmodified,)
+ props, _ignore_bad = self.session.account.session.getProperties(resource, props)
+ size = props.get(davxml.getcontentlength, "-")
+ if not size:
+ size = "0"
+ modtime = props.get(davxml.getlastmodified, "-")
+ return ["Size: %s" % (size,), "Modtime: %s" % (modtime,)]
+
+ def getAllDetails(self):
+ if self.details is None:
+ resource = URL(url=self.path+"/")
+ props = self.session.account.session.getPropertyNames(resource)
+ results, _ignore_bad = self.session.account.session.getProperties(resource, props)
+ sorted = results.keys()
+ sorted.sort()
+ self.details = [(key, results[key],) for key in sorted]
+ return self.details
+
+ def getData(self):
+ if self.data is None:
+ resource = URL(url=self.path+"/")
+ self.data, _ignore_etag = self.session.account.session.readData(resource)
+ return self.data
+
+ def getDataAsHTML(self):
+ data = self.getData()
+ if not self.type.startswith("text/html"):
+ data = "<HTML><BODY>%s</BODY></HTML>" % (data.replace("\r\n", "\n").replace("\r", "\n").replace("\n", "<br>\n"),)
+ return data
+
+ def clear(self):
+ self.children = None
+ self.details = None
+ self.data = None
Added: CalDAVClientLibrary/trunk/src/ui/session.py
===================================================================
--- CalDAVClientLibrary/trunk/src/ui/session.py (rev 0)
+++ CalDAVClientLibrary/trunk/src/ui/session.py 2008-03-25 18:56:21 UTC (rev 2248)
@@ -0,0 +1,38 @@
+##
+# Copyright (c) 2007-2008 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.
+##
+
+from client.account import CalDAVAccount
+from ui.resource import Resource
+
+class Session(object):
+ """
+ Maintains the basic information for a session and the root resource.
+ """
+
+ def __init__(self, server, user, pswd, logging):
+
+ self.server = server
+ self.user = user
+ self.pswd = pswd
+
+ # Create the account
+ ssl = server.startswith("https://")
+ server = server[8:] if ssl else server[7:]
+ paths = "/principals/users/%s/" % (self.user,)
+ self.account = CalDAVAccount(server, ssl=ssl, user=self.user, pswd=self.pswd, root=paths, principal=paths, logging=logging)
+
+ def getRoot(self):
+ return Resource(self, "/")
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20080325/0eca7122/attachment-0001.html
More information about the calendarserver-changes
mailing list