[CalendarServer-changes] [11216] CalDAVClientLibrary/trunk/caldavclientlibrary
source_changes at macosforge.org
source_changes at macosforge.org
Fri May 17 13:57:11 PDT 2013
Revision: 11216
http://trac.calendarserver.org//changeset/11216
Author: cdaboo at apple.com
Date: 2013-05-17 13:57:11 -0700 (Fri, 17 May 2013)
Log Message:
-----------
Add support for sharing and notifications processing.
Modified Paths:
--------------
CalDAVClientLibrary/trunk/caldavclientlibrary/browser/commands/__init__.py
CalDAVClientLibrary/trunk/caldavclientlibrary/browser/utils.py
CalDAVClientLibrary/trunk/caldavclientlibrary/client/clientsession.py
CalDAVClientLibrary/trunk/caldavclientlibrary/client/principal.py
CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/definitions/csxml.py
CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/makecalendar.py
CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/multiget.py
CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/query.py
CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/carddav/makeaddressbook.py
CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/carddav/multiget.py
CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/carddav/query.py
CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/acl.py
CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/lock.py
CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/principalmatch.py
CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/synccollection.py
Added Paths:
-----------
CalDAVClientLibrary/trunk/caldavclientlibrary/browser/commands/notifications.py
CalDAVClientLibrary/trunk/caldavclientlibrary/browser/commands/share.py
CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/calendarserver/
CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/calendarserver/__init__.py
CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/calendarserver/invite.py
CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/calendarserver/notifications.py
Modified: CalDAVClientLibrary/trunk/caldavclientlibrary/browser/commands/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/browser/commands/__init__.py 2013-05-17 16:56:21 UTC (rev 11215)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/browser/commands/__init__.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -30,6 +30,7 @@
"mkcal",
"mkdir",
"mv",
+ "notifications",
"principal",
"props",
"proxies",
@@ -39,6 +40,7 @@
"quota",
"rm",
"server",
+ "share",
"sync",
"user",
"whoami",
Added: CalDAVClientLibrary/trunk/caldavclientlibrary/browser/commands/notifications.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/browser/commands/notifications.py (rev 0)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/browser/commands/notifications.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -0,0 +1,249 @@
+##
+# Copyright (c) 2013 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 caldavclientlibrary.browser import commands
+from caldavclientlibrary.browser import utils
+from caldavclientlibrary.browser.command import Command
+from caldavclientlibrary.browser.command import WrongOptions
+from caldavclientlibrary.browser.subshell import SubShell
+import getopt
+import readline
+import shlex
+
+class Cmd(Command):
+
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("notifications",)
+ self.subshell = None
+
+
+ def execute(self, cmdname, options):
+
+ interactive = False
+
+ try:
+ opts, args = getopt.getopt(shlex.split(options), 'i')
+ except getopt.GetoptError, e:
+ print str(e)
+ print self.usage(cmdname)
+ raise WrongOptions
+
+ for name, _ignore_value in opts:
+
+ if name == "-i":
+ interactive = True
+ else:
+ print "Unknown option: %s" % (name,)
+ print self.usage(cmdname)
+ raise WrongOptions
+
+ if len(args) != 0:
+ print "Wrong number of arguments: %d" % (len(args),)
+ print self.usage(cmdname)
+ raise WrongOptions
+
+ principal = self.shell.account.getPrincipal(None)
+ resource = principal.notification_URL
+
+ notifications = self.shell.account.session.getNotifications(resource)
+ if not notifications:
+ print "No notifications."
+ else:
+ if interactive:
+ self.doInteractiveMode(resource, notifications)
+ else:
+ print utils.printNotificationsList(notifications, self.shell.account)
+
+ return True
+
+
+ def doInteractiveMode(self, resource, invites):
+
+ print "Entering notifications edit mode on resource: %s" % (resource.relativeURL(),)
+ if not self.subshell:
+ self.subshell = SubShell(self.shell, "Share", (
+ commands.help.Cmd(),
+ commands.logging.Cmd(),
+ commands.quit.Cmd(),
+ Accept(),
+ Decline(),
+ Delete(),
+ 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 accepting or declining invites.
+ if not present, existing notifications will be printed.
+""" % (name,)
+
+
+ def helpDescription(self):
+ return "Manage sharing notifications of an address book, calendar or address book group."
+
+
+
+class CommonNotificationsCommand(Command):
+
+ def displayNotificationsList(self):
+ # First list the current set
+ notifications = self.shell.shell.account.session.getNotifications(self.shell.resource)
+ if not notifications:
+ print "No notifications."
+ else:
+ print utils.printNotificationsList(notifications, self.shell.account)
+ return notifications
+
+
+
+class Process(CommonNotificationsCommand):
+
+ def __init__(self, accept):
+ super(Command, self).__init__()
+ self.accept = accept
+
+
+ def execute(self, name, options):
+
+ # First list the current set
+ notifications = self.displayNotificationsList()
+ if notifications:
+ # Ask user which one to delete
+ while True:
+ result = raw_input("%s invite at [1 - %d] or cancel [q]: " % (self.cmds[0].title(), len(notifications),))
+ 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) - 1
+ except ValueError:
+ print "Invalid input, try again."
+ continue
+
+ # Now execute and delete the notification if processed OK
+ if self.shell.shell.account.session.processNotification(notifications[number], self.accept):
+ self.shell.shell.account.session.deleteResource(notifications[number].url)
+ break
+
+
+
+class Accept(Process):
+
+ def __init__(self):
+ super(Accept, self).__init__(True)
+ self.cmds = ("accept",)
+
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+
+ def helpDescription(self):
+ return "Remove invite on existing resource."
+
+
+
+class Decline(Process):
+
+ def __init__(self):
+ super(Decline, self).__init__(False)
+ self.cmds = ("decline",)
+
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+
+ def helpDescription(self):
+ return "Remove invite on existing resource."
+
+
+
+class Delete(CommonNotificationsCommand):
+
+ def __init__(self):
+ super(Delete, self).__init__()
+ self.cmds = ("delete",)
+
+
+ def execute(self, name, options):
+
+ # First list the current set
+ notifications = self.displayNotificationsList()
+ if notifications:
+ # Ask user which one to delete
+ while True:
+ result = raw_input("%s invite at [1 - %d] or cancel [q]: " % (self.cmds[0].title(), len(notifications),))
+ 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) - 1
+ except ValueError:
+ print "Invalid input, try again."
+ continue
+
+ # Now delete the notification
+ self.shell.shell.account.session.deleteResource(notifications[number].url)
+ break
+
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+
+ def helpDescription(self):
+ return "Delete notification resource."
+
+
+
+class List(CommonNotificationsCommand):
+
+ def __init__(self):
+ super(List, self).__init__()
+ self.cmds = ("list",)
+
+
+ def execute(self, name, options):
+
+ self.displayNotificationsList()
+ return True
+
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+
+ def helpDescription(self):
+ return "List current invites for user."
Added: CalDAVClientLibrary/trunk/caldavclientlibrary/browser/commands/share.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/browser/commands/share.py (rev 0)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/browser/commands/share.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -0,0 +1,313 @@
+##
+# Copyright (c) 2013 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 caldavclientlibrary.browser import commands
+from caldavclientlibrary.browser import utils
+from caldavclientlibrary.browser.command import Command
+from caldavclientlibrary.browser.command import WrongOptions
+from caldavclientlibrary.browser.subshell import SubShell
+from caldavclientlibrary.protocol.url import URL
+import getopt
+import os
+import readline
+import shlex
+
+class Cmd(Command):
+
+ def __init__(self):
+ super(Command, self).__init__()
+ self.cmds = ("share",)
+ self.subshell = None
+
+
+ def execute(self, cmdname, options):
+
+ interactive = False
+ path = None
+ addressbook = False
+ calendar = False
+ group = False
+
+ try:
+ opts, args = getopt.getopt(shlex.split(options), 'acgi')
+ except getopt.GetoptError, e:
+ print str(e)
+ print self.usage(cmdname)
+ raise WrongOptions
+
+ for name, _ignore_value in opts:
+
+ if name == "-i":
+ interactive = True
+ elif name == "-a":
+ if calendar or group:
+ print "Only one of -a, -c, or -g must be present"
+ print self.usage(cmdname)
+ raise WrongOptions
+ addressbook = True
+ elif name == "-c":
+ if addressbook or group:
+ print "Only one of -a, -c, or -g must be present"
+ print self.usage(cmdname)
+ raise WrongOptions
+ calendar = True
+ elif name == "-c":
+ if addressbook or calendar:
+ print "Only one of -a, -c, or -g must be present"
+ print self.usage(cmdname)
+ raise WrongOptions
+ group = True
+ else:
+ print "Unknown option: %s" % (name,)
+ print self.usage(cmdname)
+ raise WrongOptions
+
+ if not (addressbook or calendar or group):
+ print "One of -a, -c, or -g must be present"
+ print self.usage(cmdname)
+ raise WrongOptions
+
+ if len(args) > 1:
+ print "Wrong number of arguments: %d" % (len(args),)
+ print self.usage(cmdname)
+ 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)
+
+ invites = self.shell.account.session.getInvites(resource)
+ if invites is None:
+ print "Could not retrieve CS:invite property."
+ else:
+ if interactive:
+ self.doInteractiveMode(resource, invites)
+ else:
+ print utils.printInviteList(invites, self.shell.account)
+
+ return True
+
+
+ def doInteractiveMode(self, resource, invites):
+
+ print "Entering sharing edit mode on resource: %s" % (resource.relativeURL(),)
+ if not self.subshell:
+ self.subshell = SubShell(self.shell, "Share", (
+ 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 invitees.
+ if not present, existing invitees will be printed.
+
+-a sharing for an address book
+-c sharing for a calendar
+-g sharing for an address book group
+
+One of -a, -c or -g must be present.
+""" % (name,)
+
+
+ def helpDescription(self):
+ return "Manage sharing of an address book, calendar or address book group."
+
+
+
+class CommonSharingCommand(Command):
+
+ def displayInviteList(self):
+ # First list the current set
+ invites = self.shell.shell.account.session.getInvites(self.shell.resource)
+ if invites is None:
+ print "Could not retrieve CS:invite property."
+ else:
+ print utils.printInviteList(invites, self.shell.account)
+ return invites
+
+
+ def createInvite(self, oldinvite=None):
+
+ if oldinvite is None:
+ href = utils.textInput("Enter principal id: ", None)
+ if href.startswith("user"):
+ href = "/principals/users/%s" % (href,)
+ principal = self.shell.shell.account.getPrincipal(URL(url=href))
+ user_uid = principal.principalURL.relativeURL()
+ else:
+ user_uid = oldinvite.user_uid
+
+ oldmode = "w" if oldinvite is None else ("w" if oldinvite.access == "read-write" else "r")
+ read_write = utils.choiceInput("Read or Read-Write Mode [r/w]: ", ("r", "w",), insert=oldmode)
+ read_write = (read_write == "w")
+
+ summary = utils.textInput("Summary: ", insert=(oldinvite.summary if oldinvite else "Shared"))
+
+ return user_uid, read_write, summary
+
+
+
+class Add(CommonSharingCommand):
+
+ def __init__(self):
+ super(Add, self).__init__()
+ self.cmds = ("add",)
+
+
+ def execute(self, name, options):
+
+ # Try and get the new details
+ user_uid, read_write, summary = self.createInvite()
+
+ # Now execute
+ self.shell.shell.account.session.addInvitee(self.shell.resource, user_uid, read_write, summary)
+
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+
+ def helpDescription(self):
+ return "Add invite to existing resource."
+
+
+
+class Change(CommonSharingCommand):
+
+ def __init__(self):
+ super(Change, self).__init__()
+ self.cmds = ("change",)
+
+
+ def execute(self, name, options):
+
+ # First list the current set
+ invites = self.displayInviteList()
+ if len(invites.invitees):
+ # Ask user which one to delete
+ while True:
+ result = raw_input("Change invite at [1 - %d] or cancel [q]: " % (len(invites.invitees),))
+ 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) - 1
+ except ValueError:
+ print "Invalid input, try again."
+ continue
+
+ # Try and get the new details
+ user_uid, read_write, summary = self.createInvite(invites.invitees[number])
+
+ # Now execute
+ self.shell.shell.account.session.addInvitee(self.shell.resource, user_uid, read_write, summary)
+ break
+
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+
+ def helpDescription(self):
+ return "Change invite on existing resource."
+
+
+
+class Remove(CommonSharingCommand):
+
+ def __init__(self):
+ super(Remove, self).__init__()
+ self.cmds = ("remove",)
+
+
+ def execute(self, name, options):
+
+ # First list the current set
+ invites = self.displayInviteList()
+ if len(invites.invitees):
+ # Ask user which one to delete
+ while True:
+ result = raw_input("Remove invite [1 - %d] or cancel [q]: " % (len(invites.invitees),))
+ 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) - 1
+ except ValueError:
+ print "Invalid input, try again."
+ continue
+
+ # Now execute
+ self.shell.shell.account.session.removeInvitee(self.shell.resource, invites.invitees[number])
+ break
+
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+
+ def helpDescription(self):
+ return "Remove invite on existing resource."
+
+
+
+class List(CommonSharingCommand):
+
+ def __init__(self):
+ super(List, self).__init__()
+ self.cmds = ("list",)
+
+
+ def execute(self, name, options):
+
+ self.displayInviteList()
+ return True
+
+
+ def usage(self, name):
+ return """Usage: %s
+""" % (name,)
+
+
+ def helpDescription(self):
+ return "List current invitees on existing resource."
Modified: CalDAVClientLibrary/trunk/caldavclientlibrary/browser/utils.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/browser/utils.py 2013-05-17 16:56:21 UTC (rev 11215)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/browser/utils.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -14,6 +14,8 @@
# limitations under the License.
##
+from caldavclientlibrary.protocol.calendarserver.notifications import InviteNotification, \
+ InviteReply
from caldavclientlibrary.protocol.url import URL
from caldavclientlibrary.protocol.webdav.definitions import davxml
import readline
@@ -162,6 +164,59 @@
+def printInviteList(invites, account):
+
+ result = "Organizer: %s (%s)" % (invites.organizer_cn, invites.organizer_uid,)
+ for ctr, user in enumerate(invites.invitees):
+ result += "\n% 2d. %s" % (ctr + 1, printInviteUser(user, account))
+ return result
+
+
+
+def printInviteUser(user, account):
+
+ return "%s (%s) Invite: %s Access: %s Summary: %s" % (
+ user.user_cn,
+ user.user_uid,
+ user.mode,
+ user.access,
+ user.summary,
+ )
+
+
+
+def printNotificationsList(notifications, account):
+
+ result = ""
+ if notifications:
+ for ctr, notification in enumerate(notifications):
+ result += "\n% 2d. %s" % (ctr + 1, printNotification(notification, account))
+ else:
+ "No notifications."
+ return result
+
+
+
+def printNotification(notification, account):
+
+ if isinstance(notification, InviteNotification):
+ return "Sharing Invite: From: %s (%s) Access: %s Summary: %s Host-URL: %s" % (
+ notification.organizer_cn,
+ notification.organizer_uid,
+ notification.access,
+ notification.summary,
+ notification.hosturl,
+ )
+ elif isinstance(notification, InviteReply):
+ return "Sharing Reply: From: %s Result: %s Summary: %s Host-URL: %s" % (
+ notification.user_uid,
+ notification.mode,
+ notification.summary,
+ notification.hosturl,
+ )
+
+
+
def textInput(title, insert=None):
if insert:
title = "%s [%s]:" % (title, insert,)
Modified: CalDAVClientLibrary/trunk/caldavclientlibrary/client/clientsession.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/client/clientsession.py 2013-05-17 16:56:21 UTC (rev 11215)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/client/clientsession.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -15,14 +15,19 @@
# #
from caldavclientlibrary.client.httpshandler import SmartHTTPConnection
-from caldavclientlibrary.protocol.caldav.definitions import headers
+from caldavclientlibrary.protocol.caldav.definitions import headers, csxml
from caldavclientlibrary.protocol.caldav.makecalendar import MakeCalendar
from caldavclientlibrary.protocol.caldav.multiget import Multiget as CalMultiget
from caldavclientlibrary.protocol.caldav.query import QueryVEVENTTimeRange
+from caldavclientlibrary.protocol.calendarserver.invite import RemoveInvitee, Invites, \
+ AddInvitee
from caldavclientlibrary.protocol.carddav.makeaddressbook import MakeAddressBook
from caldavclientlibrary.protocol.carddav.multiget import Multiget as AdbkMultiget
from caldavclientlibrary.protocol.http.authentication.basic import Basic
from caldavclientlibrary.protocol.http.authentication.digest import Digest
+import urllib
+from caldavclientlibrary.protocol.calendarserver.notifications import InviteNotification, \
+ InviteReply, ProcessNotification
try:
from caldavclientlibrary.protocol.http.authentication.gssapi import Kerberos
except ImportError:
@@ -46,7 +51,7 @@
from caldavclientlibrary.protocol.webdav.put import Put
from caldavclientlibrary.protocol.webdav.session import Session
from caldavclientlibrary.protocol.webdav.synccollection import SyncCollection
-from xml.etree.ElementTree import Element, tostring
+from xml.etree.ElementTree import Element, tostring, XML
import types
class CalDAVSession(Session):
@@ -751,6 +756,133 @@
self.handleHTTPError(request)
+ def getInvites(self, rurl):
+ """
+ Get the invitation details for the specified resource by reading and parsing the CS:invite WebDAV property.
+
+ @param rurl: the resource whose property is to be read
+ @type rurl: L{URL}
+ """
+
+ assert(isinstance(rurl, URL))
+
+ results, bad = self.getProperties(rurl, (csxml.invite,))
+ if csxml.invite in bad:
+ return None
+ else:
+ return Invites().parseFromInvite(results.get(csxml.invite))
+
+
+ def addInvitee(self, rurl, user_uid, read_write, summary=None):
+ """
+ Add a sharing invite for the specified resource.
+
+ @param rurl: the resource to share
+ @type rurl: L{URL}
+ @param user_uid: short name or full principal path of user to share with
+ @type user_uid: C{str}
+ @param read_write: whether to share read-only C{False} or read-write C{True}
+ @type read_write: C{bool}
+ @param summary: summary description for the share
+ @type summary: C{str}
+ """
+
+ assert(isinstance(rurl, URL))
+
+ # Add invitation POST
+ request = AddInvitee(self, rurl.relativeURL(), user_uid, read_write, summary)
+
+ # Process it
+ self.runSession(request)
+
+ if request.getStatusCode() not in (statuscodes.OK, statuscodes.NoContent,):
+ self.handleHTTPError(request)
+
+
+ def removeInvitee(self, rurl, invitee):
+ """
+ Remove an invite from a shared resource.
+
+ @param rurl: the resource currently being shared
+ @type rurl: L{URL}
+ @param invitee: invite DAV:href for the user being removed
+ @type invitee: C{str}
+ """
+
+ assert(isinstance(rurl, URL))
+
+ # Remove invitation POST
+ request = RemoveInvitee(self, rurl.relativeURL(), invitee)
+
+ # Process it
+ self.runSession(request)
+
+ if request.getStatusCode() not in (statuscodes.OK, statuscodes.NoContent,):
+ self.handleHTTPError(request)
+
+
+ def getNotifications(self, rurl):
+ """
+ Get a list of L{Notification} objects for the specified notification collection.
+
+ @param rurl: a user's notification collection URL
+ @type rurl: L{URL}
+ """
+
+ assert(isinstance(rurl, URL))
+
+ # List all children of the notification collection
+ results = self.getPropertiesOnHierarchy(rurl, (davxml.getcontenttype,))
+ items = results.keys()
+ items.sort()
+ notifications = []
+ for path in items:
+ path = urllib.unquote(path)
+ nurl = URL(url=path)
+ if rurl == nurl:
+ continue
+ props = results[path]
+ if props.get(davxml.getcontenttype, "none").split(";")[0] in ("text/xml", "application/xml"):
+ data, _ignore_etag = self.readData(URL(url=path))
+ node = XML(data)
+ if node.tag == str(csxml.notification):
+ for child in node.getchildren():
+ if child.tag == str(csxml.invite_notification):
+ notifications.append(InviteNotification().parseFromNotification(nurl, child))
+ elif child.tag == str(csxml.invite_reply):
+ notifications.append(InviteReply().parseFromNotification(nurl, child))
+
+ return notifications
+
+
+ def processNotification(self, notification, accept):
+ """
+ Accept or decline a sharing invite in the specified notification.
+
+ @param notification: the notification
+ @type notification: L{InviteNotification}
+ @param accept: whether to accept C{True} or decline C{False} the invite
+ @type accept: C{bool}
+ """
+
+ assert(isinstance(notification.url, URL))
+
+ # POST goes to home which is two segments up from the notification resource
+ rurl = notification.url.dirname().dirname()
+
+ # Add invitation POST
+ request = ProcessNotification(self, rurl.relativeURL(), notification, accept)
+
+ # Process it
+ self.runSession(request)
+
+ if request.getStatusCode() not in (statuscodes.OK, statuscodes.NoContent,):
+ self.handleHTTPError(request)
+ return False
+ else:
+ return True
+
+
def addAttachment(self, rurl, filename, data, contentType, return_representation):
assert(isinstance(rurl, URL))
Modified: CalDAVClientLibrary/trunk/caldavclientlibrary/client/principal.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/client/principal.py 2013-05-17 16:56:21 UTC (rev 11215)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/client/principal.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -16,7 +16,7 @@
from caldavclientlibrary.client.addressbook import AddressBook
from caldavclientlibrary.client.calendar import Calendar
-from caldavclientlibrary.protocol.caldav.definitions import caldavxml
+from caldavclientlibrary.protocol.caldav.definitions import caldavxml, csxml
from caldavclientlibrary.protocol.caldav.definitions import headers
from caldavclientlibrary.protocol.carddav.definitions import carddavxml
from caldavclientlibrary.protocol.url import URL
@@ -111,6 +111,7 @@
self.inboxURL = ""
self.cuaddrs = ()
self.adbkhomeset = ()
+ self.notificationsURL = ""
self.proxyFor = None
self.proxyreadURL = ""
@@ -136,6 +137,7 @@
caldavxml.schedule_inbox_URL,
caldavxml.calendar_user_address_set,
carddavxml.addressbook_home_set,
+ csxml.notification_URL,
),
)
if results:
@@ -159,6 +161,7 @@
self.inboxURL = results.get(caldavxml.schedule_inbox_URL, None)
self.cuaddrs = make_tuple(results.get(caldavxml.calendar_user_address_set, ()))
self.adbkhomeset = make_tuple(results.get(carddavxml.addressbook_home_set, ()))
+ self.notification_URL = results.get(csxml.notification_URL, None)
# Get proxy resource details if proxy support is available
if self.session.hasDAVVersion(headers.calendar_proxy) and not self.proxyFor:
Modified: CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/definitions/csxml.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/definitions/csxml.py 2013-05-17 16:56:21 UTC (rev 11215)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/definitions/csxml.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -33,3 +33,35 @@
xmpp_server = QName(CSNamespace, "xmpp-server")
xmpp_uri = QName(CSNamespace, "xmpp-uri")
pushkey = QName(CSNamespace, "pushkey")
+
+# Sharing
+invite = QName(CSNamespace, "invite")
+organizer = QName(CSNamespace, "organizer")
+user = QName(CSNamespace, "user")
+common_name = QName(CSNamespace, "common-name")
+first_name = QName(CSNamespace, "first-name")
+last_name = QName(CSNamespace, "last-name")
+
+invite_noresponse = QName(CSNamespace, "invite-noresponse")
+invite_accepted = QName(CSNamespace, "invite-accepted")
+invite_declined = QName(CSNamespace, "invite-declined")
+invite_invalid = QName(CSNamespace, "invite-invalid")
+
+access = QName(CSNamespace, "access")
+read = QName(CSNamespace, "read")
+summary = QName(CSNamespace, "summary")
+
+read_write = QName(CSNamespace, "read-write")
+
+share = QName(CSNamespace, "share")
+set = QName(CSNamespace, "set")
+remove = QName(CSNamespace, "remove")
+
+notification_URL = QName(CSNamespace, "notification-URL")
+notification = QName(CSNamespace, "notification")
+dtstamp = QName(CSNamespace, "dtstamp")
+invite_notification = QName(CSNamespace, "invite-notification")
+invite_reply = QName(CSNamespace, "invite-reply")
+uid = QName(CSNamespace, "uid")
+hosturl = QName(CSNamespace, "hosturl")
+in_reply_to = QName(CSNamespace, "in-reply-to")
Modified: CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/makecalendar.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/makecalendar.py 2013-05-17 16:56:21 UTC (rev 11215)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/makecalendar.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -40,7 +40,7 @@
# Write XML info to a string
os = StringIO()
self.generateXML(os)
- self.request_data = RequestDataString(os.getvalue(), "text/xml charset=utf-8")
+ self.request_data = RequestDataString(os.getvalue(), "text/xml;charset=utf-8")
def generateXML(self, os):
Modified: CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/multiget.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/multiget.py 2013-05-17 16:56:21 UTC (rev 11215)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/multiget.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -37,7 +37,7 @@
# Write XML info to a string
os = StringIO()
self.generateXML(os)
- self.request_data = RequestDataString(os.getvalue(), "text/xml charset=utf-8")
+ self.request_data = RequestDataString(os.getvalue(), "text/xml;charset=utf-8")
def generateXML(self, os):
Modified: CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/query.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/query.py 2013-05-17 16:56:21 UTC (rev 11215)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/caldav/query.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -36,7 +36,7 @@
# Write XML info to a string
os = StringIO()
self.generateXML(os)
- self.request_data = RequestDataString(os.getvalue(), "text/xml charset=utf-8")
+ self.request_data = RequestDataString(os.getvalue(), "text/xml;charset=utf-8")
def generateXML(self, os):
Added: CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/calendarserver/__init__.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/calendarserver/__init__.py (rev 0)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/calendarserver/__init__.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2013 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/caldavclientlibrary/protocol/calendarserver/invite.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/calendarserver/invite.py (rev 0)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/calendarserver/invite.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -0,0 +1,198 @@
+##
+# Copyright (c) 2013 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 caldavclientlibrary.protocol.caldav.definitions import csxml
+from caldavclientlibrary.protocol.webdav.definitions import davxml
+from caldavclientlibrary.protocol.webdav.post import Post
+from caldavclientlibrary.protocol.http.data.string import RequestDataString
+from xml.etree.ElementTree import Element, SubElement
+from caldavclientlibrary.protocol.utils.xmlhelpers import BetterElementTree
+
+
+def userNameFromNode(node):
+ cn = node.find(str(csxml.common_name))
+ first = node.find(str(csxml.first_name))
+ last = node.find(str(csxml.last_name))
+
+ return cn.text if cn is not None else ("%s %s" % (first.text, last.text) if first is not None or last is not None else "")
+
+
+
+class Invites(object):
+ """
+ Represents a list of invites to sharees for a shared resource.
+ """
+
+ def __init__(self):
+
+ self.organizer_uid = None
+ self.organizer_cn = ""
+ self.invitees = []
+
+
+ def parseFromInvite(self, invite):
+
+ if invite is not None:
+ organizer = invite.find(str(csxml.organizer))
+ if organizer is not None:
+ self.organizer_uid = organizer.find(str(davxml.href)).text
+ self.organizer_cn = userNameFromNode(organizer)
+
+ for user in invite.findall(str(csxml.user)):
+ self.invitees.append(InviteUser().parseFromUser(user))
+
+ return self
+
+
+
+class InviteUser(object):
+ """
+ An invite for a specific sharee.
+ """
+
+ def __init__(self):
+
+ self.user_uid = None
+ self.user_cn = ""
+ self.mode = "unknown"
+ self.access = "unknown"
+ self.summary = "-"
+
+
+ def parseFromUser(self, user):
+
+ self.user_uid = user.find(str(davxml.href)).text
+ self.user_cn = userNameFromNode(user)
+ if user.find(str(csxml.invite_noresponse)) is not None:
+ self.mode = "no-response"
+ elif user.find(str(csxml.invite_accepted)) is not None:
+ self.mode = "accepted"
+ elif user.find(str(csxml.invite_declined)) is not None:
+ self.mode = "declined"
+ elif user.find(str(csxml.invite_invalid)) is not None:
+ self.mode = "invalid"
+
+ access = user.find(str(csxml.access))
+ if access.find(str(csxml.read)) is not None:
+ self.access = "read"
+ elif access.find(str(csxml.read_write)) is not None:
+ self.access = "read-write"
+
+ summary = user.find(str(csxml.summary))
+ if summary is not None:
+ self.summary = summary.text
+
+ return self
+
+
+
+class AddInvitee(Post):
+ """
+ HTTP POST request to add an invite for a sharee.
+ """
+
+ def __init__(self, session, url, user_uid, read_write, summary=None):
+ super(AddInvitee, self).__init__(session, url)
+ self.user_uid = user_uid
+ self.read_write = read_write
+ self.summary = summary if summary is not None else "-"
+
+ 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:
+ #
+ # <CS:share>
+ # <CS:set>
+ # <DAV:href>...</DAV:href>
+ # <CS:summary>...</CS:summary>
+ # <CS:read /> | <CS:read-write />
+ # </CS:set>
+ # </CS:share>
+
+ # <CS:share> element
+ share = Element(csxml.share)
+
+ # <CS:set> element
+ set = SubElement(share, csxml.set)
+
+ # <DAV:href> element
+ href = SubElement(set, davxml.href)
+ href.text = self.user_uid
+
+ # <CS:summary> element
+ summary = SubElement(set, csxml.summary)
+ summary.text = self.summary
+
+ # <CS:read /> | <CS:read-write />
+ SubElement(set, csxml.read_write if self.read_write else csxml.read)
+
+ # Now we have the complete document, so write it out (no indentation)
+ xmldoc = BetterElementTree(share)
+ xmldoc.writeUTF8(os)
+
+
+
+class RemoveInvitee(Post):
+ """
+ HTTP POST request to remove an invite for a sharee.
+ """
+
+ def __init__(self, session, url, invitee):
+ super(RemoveInvitee, self).__init__(session, url)
+ self.invitee = invitee
+
+ 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:
+ #
+ # <CS:share>
+ # <CS:remove>
+ # <DAV:href>...</DAV:href>
+ # </CS:remove>
+ # </CS:share>
+
+ # <CS:share> element
+ share = Element(csxml.share)
+
+ # <CS:remove> element
+ remove = SubElement(share, csxml.remove)
+
+ # <DAV:href> element
+ href = SubElement(remove, davxml.href)
+ href.text = self.invitee.user_uid
+
+ # Now we have the complete document, so write it out (no indentation)
+ xmldoc = BetterElementTree(share)
+ xmldoc.writeUTF8(os)
Added: CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/calendarserver/notifications.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/calendarserver/notifications.py (rev 0)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/calendarserver/notifications.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -0,0 +1,177 @@
+##
+# Copyright (c) 2013 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 caldavclientlibrary.protocol.caldav.definitions import csxml
+from caldavclientlibrary.protocol.webdav.definitions import davxml
+from caldavclientlibrary.protocol.webdav.post import Post
+from caldavclientlibrary.protocol.http.data.string import RequestDataString
+from xml.etree.ElementTree import Element, SubElement
+from caldavclientlibrary.protocol.utils.xmlhelpers import BetterElementTree
+
+
+def userNameFromNode(node):
+ cn = node.find(str(csxml.common_name))
+ first = node.find(str(csxml.first_name))
+ last = node.find(str(csxml.last_name))
+
+ return cn.text if cn is not None else ("%s %s" % (first.text, last.text) if first is not None or last is not None else "")
+
+
+
+class InviteNotification(object):
+ """
+ An invite notification sent to sharees.
+ """
+
+ def __init__(self):
+
+ self.url = None
+ self.uid = ""
+ self.user_uid = ""
+ self.access = "unknown"
+ self.hosturl = ""
+ self.organizer_uid = None
+ self.organizer_cn = None
+ self.summary = "-"
+
+
+ def parseFromNotification(self, url, notification):
+
+ self.url = url
+
+ self.uid = notification.find(str(csxml.uid)).text
+ self.user_uid = notification.find(str(davxml.href)).text
+
+ access = notification.find(str(csxml.access))
+ if access.find(str(csxml.read)) is not None:
+ self.access = "read"
+ elif access.find(str(csxml.read_write)) is not None:
+ self.access = "read-write"
+
+ hosturl = notification.find(str(csxml.hosturl))
+ if hosturl is not None:
+ self.hosturl = hosturl.find(str(davxml.href)).text
+
+ organizer = notification.find(str(csxml.organizer))
+ if organizer is not None:
+ self.organizer_uid = organizer.find(str(davxml.href)).text
+ self.organizer_cn = userNameFromNode(organizer)
+
+ summary = notification.find(str(csxml.summary))
+ if summary is not None:
+ self.summary = summary.text
+
+ return self
+
+
+
+class InviteReply(object):
+ """
+ An invite reply sent to the sharer.
+ """
+
+ def __init__(self):
+
+ self.url = None
+ self.user_uid = ""
+ self.mode = "unknown"
+ self.hosturl = ""
+ self.in_reply_to = ""
+ self.summary = "-"
+
+
+ def parseFromNotification(self, url, notification):
+
+ self.url = url
+
+ self.user_uid = notification.find(str(davxml.href)).text
+
+ if notification.find(str(csxml.invite_accepted)) is not None:
+ self.mode = "accepted"
+ elif notification.find(str(csxml.invite_declined)) is not None:
+ self.mode = "declined"
+
+ hosturl = notification.find(str(csxml.hosturl))
+ if hosturl is not None:
+ self.hosturl = hosturl.find(str(davxml.href)).text
+
+ in_reply_to = notification.find(str(csxml.in_reply_to))
+ if in_reply_to is not None:
+ self.in_reply_to = in_reply_to.text
+
+ summary = notification.find(str(csxml.summary))
+ if summary is not None:
+ self.summary = summary.text
+
+ return self
+
+
+
+class ProcessNotification(Post):
+ """
+ HTTP POST request to accept or decline a sharee's invite notification.
+ """
+
+ def __init__(self, session, url, notification, accepted):
+ super(ProcessNotification, self).__init__(session, url)
+ self.notification = notification
+ self.accepted = accepted
+
+ 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:
+ #
+ # <CS:invite-reply>
+ # <DAV:href>...</DAV:href>
+ # <CS:invite-accepted /> | <CS:invite-declined />
+ # <CS:hosturl>...</CS:hosturl>
+ # <CS:in-reply-to>...</CS:in-reply-to>
+ # </CS:invite-reply>
+
+ # <CS:invite-reply> element
+ invite_reply = Element(csxml.invite_reply)
+
+ # <DAV:href> element
+ href = SubElement(invite_reply, davxml.href)
+ href.text = self.notification.user_uid
+
+ # <CS:invite-accepted /> | <CS:invite-declined />
+ SubElement(invite_reply, csxml.invite_accepted if self.accepted else csxml.invite_declined)
+
+ # <CS:hosturl> element
+ hosturl = SubElement(invite_reply, csxml.hosturl)
+
+ # <DAV:href> element
+ href = SubElement(hosturl, davxml.href)
+ href.text = self.notification.hosturl
+
+ # <CS:in-reply-to> element
+ in_reply_to = SubElement(invite_reply, csxml.in_reply_to)
+ in_reply_to.text = self.notification.uid
+
+ # Now we have the complete document, so write it out (no indentation)
+ xmldoc = BetterElementTree(invite_reply)
+ xmldoc.writeUTF8(os)
Modified: CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/carddav/makeaddressbook.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/carddav/makeaddressbook.py 2013-05-17 16:56:21 UTC (rev 11215)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/carddav/makeaddressbook.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -37,7 +37,7 @@
# Write XML info to a string
os = StringIO()
self.generateXML(os)
- self.request_data = RequestDataString(os.getvalue(), "text/xml charset=utf-8")
+ self.request_data = RequestDataString(os.getvalue(), "text/xml;charset=utf-8")
def generateXML(self, os):
Modified: CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/carddav/multiget.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/carddav/multiget.py 2013-05-17 16:56:21 UTC (rev 11215)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/carddav/multiget.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -37,7 +37,7 @@
# Write XML info to a string
os = StringIO()
self.generateXML(os)
- self.request_data = RequestDataString(os.getvalue(), "text/xml charset=utf-8")
+ self.request_data = RequestDataString(os.getvalue(), "text/xml;charset=utf-8")
def generateXML(self, os):
Modified: CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/carddav/query.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/carddav/query.py 2013-05-17 16:56:21 UTC (rev 11215)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/carddav/query.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -36,7 +36,7 @@
# Write XML info to a string
os = StringIO()
self.generateXML(os)
- self.request_data = RequestDataString(os.getvalue(), "text/xml charset=utf-8")
+ self.request_data = RequestDataString(os.getvalue(), "text/xml;charset=utf-8")
def generateXML(self, os):
Modified: CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/acl.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/acl.py 2013-05-17 16:56:21 UTC (rev 11215)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/acl.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -35,7 +35,7 @@
# Write XML info to a string
os = StringIO()
self.generateXML(os)
- self.request_data = RequestDataString(os.getvalue(), "text/xml charset=utf-8")
+ self.request_data = RequestDataString(os.getvalue(), "text/xml;charset=utf-8")
def generateXML(self, os):
Modified: CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/lock.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/lock.py 2013-05-17 16:56:21 UTC (rev 11215)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/lock.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -62,7 +62,7 @@
# Write XML info to a string
os = StringIO()
self.generateXML(os)
- self.request_data = RequestDataString(os.getvalue(), "text/xml charset=utf-8")
+ self.request_data = RequestDataString(os.getvalue(), "text/xml;charset=utf-8")
def addHeaders(self, hdrs):
Modified: CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/principalmatch.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/principalmatch.py 2013-05-17 16:56:21 UTC (rev 11215)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/principalmatch.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -38,7 +38,7 @@
# Write XML info to a string
os = StringIO()
self.generateXML(os)
- self.request_data = RequestDataString(os.getvalue(), "text/xml charset=utf-8")
+ self.request_data = RequestDataString(os.getvalue(), "text/xml;charset=utf-8")
def generateXML(self, os):
Modified: CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/synccollection.py
===================================================================
--- CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/synccollection.py 2013-05-17 16:56:21 UTC (rev 11215)
+++ CalDAVClientLibrary/trunk/caldavclientlibrary/protocol/webdav/synccollection.py 2013-05-17 20:57:11 UTC (rev 11216)
@@ -39,7 +39,7 @@
# Write XML info to a string
os = StringIO()
self.generateXML(os)
- self.request_data = RequestDataString(os.getvalue(), "text/xml charset=utf-8")
+ self.request_data = RequestDataString(os.getvalue(), "text/xml;charset=utf-8")
def addHeaders(self, hdrs):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130517/ede2a3d9/attachment-0001.html>
More information about the calendarserver-changes
mailing list