<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[15309] CalendarServer/branches/release/CalendarServer-5.4-dev</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/15309">15309</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2015-11-13 09:49:04 -0800 (Fri, 13 Nov 2015)</dd>
</dl>
<h3>Log Message</h3>
<pre>Enhanced X- property/parameter whitelisting.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesreleaseCalendarServer54devconfcaldavdtestplist">CalendarServer/branches/release/CalendarServer-5.4-dev/conf/caldavd-test.plist</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer54devtwistedcaldavicalpy">CalendarServer/branches/release/CalendarServer-5.4-dev/twistedcaldav/ical.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer54devtwistedcaldavstdconfigpy">CalendarServer/branches/release/CalendarServer-5.4-dev/twistedcaldav/stdconfig.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer54devtxdavcaldavdatastoreschedulingicaldiffpy">CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/icaldiff.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer54devtxdavcaldavdatastoreschedulingitippy">CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/itip.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer54devtxdavcaldavdatastoreschedulingprocessingpy">CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/processing.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer54devtxdavcaldavdatastoreschedulingtesttest_itippy">CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/test/test_itip.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer54devtxdavcaldavdatastoresqlpy">CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/sql.py</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesreleaseCalendarServer54devconfcaldavdtestplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.4-dev/conf/caldavd-test.plist (15308 => 15309)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.4-dev/conf/caldavd-test.plist        2015-11-13 17:46:32 UTC (rev 15308)
+++ CalendarServer/branches/release/CalendarServer-5.4-dev/conf/caldavd-test.plist        2015-11-13 17:49:04 UTC (rev 15309)
</span><span class="lines">@@ -709,6 +709,40 @@
</span><span class="cx"> <true/>
</span><span class="cx"> <key>EnablePrivateComments</key>
</span><span class="cx"> <true/>
</span><ins>+ <key>OrganizerPublicProperties</key>
+ <array>
+         <string>X-APPLE-DROPBOX</string>
+         <string>X-APPLE-STRUCTURED-LOCATION</string>
+         <string>X-TEST-ORGANIZER-PROP1</string>
+         <string>X-TEST-ORGANIZER-PROP2</string>
+         <string>X-TEST-ORGANIZER-PROP3</string>
+         <string>X-TEST-ORGANIZER-PROP4</string>
+         <string>X-TEST-ORGANIZER-PROP5</string>
+ </array>
+ <key>OrganizerPublicParameters</key>
+ <array>
+         <string>X-TEST-ORGANIZER-PARAM1</string>
+         <string>X-TEST-ORGANIZER-PARAM2</string>
+         <string>X-TEST-ORGANIZER-PARAM3</string>
+         <string>X-TEST-ORGANIZER-PARAM4</string>
+         <string>X-TEST-ORGANIZER-PARAM5</string>
+ </array>
+ <key>AttendeePublicProperties</key>
+ <array>
+         <string>X-TEST-ALL-PROP1</string>
+         <string>X-TEST-ALL-PROP2</string>
+         <string>X-TEST-ALL-PROP3</string>
+         <string>X-TEST-ALL-PROP4</string>
+         <string>X-TEST-ALL-PROP5</string>
+ </array>
+ <key>AttendeePublicParameters</key>
+ <array>
+         <string>X-TEST-ALL-PARAM1</string>
+         <string>X-TEST-ALL-PARAM2</string>
+         <string>X-TEST-ALL-PARAM3</string>
+         <string>X-TEST-ALL-PARAM4</string>
+         <string>X-TEST-ALL-PARAM5</string>
+ </array>
</ins><span class="cx"> </dict>
</span><span class="cx">
</span><span class="cx"> <!-- iSchedule protocol options -->
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer54devtwistedcaldavicalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.4-dev/twistedcaldav/ical.py (15308 => 15309)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.4-dev/twistedcaldav/ical.py        2015-11-13 17:46:32 UTC (rev 15308)
+++ CalendarServer/branches/release/CalendarServer-5.4-dev/twistedcaldav/ical.py        2015-11-13 17:49:04 UTC (rev 15309)
</span><span class="lines">@@ -78,6 +78,11 @@
</span><span class="cx"> PERUSER_UID = "X-CALENDARSERVER-PERUSER-UID"
</span><span class="cx"> PERINSTANCE_COMPONENT = "X-CALENDARSERVER-PERINSTANCE"
</span><span class="cx">
</span><ins>+PRIVATE_COMMENT = "X-CALENDARSERVER-PRIVATE-COMMENT"
+ATTENDEE_COMMENT = "X-CALENDARSERVER-ATTENDEE-COMMENT"
+ATTENDEE_COMMENT_REF = "X-CALENDARSERVER-ATTENDEE-REF"
+DTSTAMP_PARAM = "X-CALENDARSERVER-DTSTAMP"
+
</ins><span class="cx"> # 2445 default values and parameters
</span><span class="cx"> # Structure: propname: (<default value>, <parameter defaults dict>)
</span><span class="cx">
</span><span class="lines">@@ -964,6 +969,16 @@
</span><span class="cx"> self._markAsDirty()
</span><span class="cx">
</span><span class="cx">
</span><ins>+ def removeProperties(self, name):
+ """
+ remove all properties with name
+ @param name: the name of the properties to remove.
+ """
+ self._pycalendar.removeProperties(name)
+ self._pycalendar.finalise()
+ self._markAsDirty()
+
+
</ins><span class="cx"> def removeAllPropertiesWithName(self, pname):
</span><span class="cx"> """
</span><span class="cx"> Remove all properties with the given name from all components.
</span><span class="lines">@@ -2866,24 +2881,25 @@
</span><span class="cx"> self.removeComponent(component)
</span><span class="cx">
</span><span class="cx">
</span><del>- def removeXProperties(self, keep_properties=(), remove_x_parameters=True, do_subcomponents=True):
</del><ins>+ def removeXProperties(self, keep_properties=(), keep_parameters=(), do_subcomponents=True):
</ins><span class="cx"> """
</span><span class="cx"> Remove all X- properties except the specified ones
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> if do_subcomponents and self.name() == "VCALENDAR":
</span><span class="cx"> for component in self.subcomponents():
</span><del>- component.removeXProperties(keep_properties, remove_x_parameters, do_subcomponents=False)
</del><ins>+ component.removeXProperties(keep_properties, keep_parameters, do_subcomponents=False)
</ins><span class="cx"> else:
</span><span class="cx"> if self.name() in ignoredComponents:
</span><span class="cx"> return
</span><span class="cx"> for p in tuple(self.properties()):
</span><del>- xpname = p.name().startswith("X-")
- if xpname and p.name() not in keep_properties:
</del><ins>+ pname = p.name()
+ xpname = pname.startswith("X-")
+ if xpname and pname not in keep_properties:
</ins><span class="cx"> self.removeProperty(p)
</span><del>- elif not xpname and remove_x_parameters:
</del><ins>+ elif not xpname:
</ins><span class="cx"> for paramname in p.parameterNames():
</span><del>- if paramname.startswith("X-"):
</del><ins>+ if paramname.startswith("X-") and paramname not in keep_parameters:
</ins><span class="cx"> p.removeParameter(paramname)
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -3425,8 +3441,8 @@
</span><span class="cx"> return True
</span><span class="cx"> else:
</span><span class="cx"> attendee_refs = set()
</span><del>- for prop in tuple(self.properties("X-CALENDARSERVER-ATTENDEE-COMMENT")):
- ref = prop.parameterValue("X-CALENDARSERVER-ATTENDEE-REF")
</del><ins>+ for prop in tuple(self.properties(ATTENDEE_COMMENT)):
+ ref = prop.parameterValue(ATTENDEE_COMMENT_REF)
</ins><span class="cx"> if ref in attendee_refs:
</span><span class="cx"> if doFix:
</span><span class="cx"> self.removeProperty(prop)
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer54devtwistedcaldavstdconfigpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.4-dev/twistedcaldav/stdconfig.py (15308 => 15309)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.4-dev/twistedcaldav/stdconfig.py        2015-11-13 17:46:32 UTC (rev 15308)
+++ CalendarServer/branches/release/CalendarServer-5.4-dev/twistedcaldav/stdconfig.py        2015-11-13 17:49:04 UTC (rev 15309)
</span><span class="lines">@@ -685,6 +685,14 @@
</span><span class="cx"> "X-APPLE-DROPBOX",
</span><span class="cx"> "X-APPLE-STRUCTURED-LOCATION",
</span><span class="cx"> ],
</span><ins>+ "OrganizerPublicParameters" : [ # Names of X- iCalendar parameters that are sent from ORGANIZER to ATTENDEE
+ ],
+ "AttendeePublicProperties" : [ # Names of X- iCalendar properties that are sent from ATTENDEE to ORGANIZER
+ # These are also implicitly added to OrganizerPublicProperties
+ ],
+ "AttendeePublicParameters" : [ # Names of X- iCalendar parameters that are sent from ATTENDEE to ORGANIZER
+ # These are also implicitly added to OrganizerPublicParameters
+ ],
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> "iSchedule": {
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer54devtxdavcaldavdatastoreschedulingicaldiffpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/icaldiff.py (15308 => 15309)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/icaldiff.py        2015-11-13 17:46:32 UTC (rev 15308)
+++ CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/icaldiff.py        2015-11-13 17:49:04 UTC (rev 15309)
</span><span class="lines">@@ -23,7 +23,7 @@
</span><span class="cx">
</span><span class="cx"> from twistedcaldav import accounting
</span><span class="cx"> from twistedcaldav.config import config
</span><del>-from twistedcaldav.ical import Component, Property
</del><ins>+from twistedcaldav.ical import Component, Property, PRIVATE_COMMENT, DTSTAMP_PARAM
</ins><span class="cx"> from txdav.caldav.datastore.scheduling.cuaddress import normalizeCUAddr
</span><span class="cx"> from txdav.caldav.datastore.scheduling.itip import iTipGenerator
</span><span class="cx">
</span><span class="lines">@@ -41,7 +41,6 @@
</span><span class="cx"> change is being triggered by an Organizer or an Attendee.
</span><span class="cx"> """
</span><span class="cx">
</span><del>-
</del><span class="cx"> def __init__(self, oldcalendar, newcalendar, smart_merge, forceTRANSP=False):
</span><span class="cx"> """
</span><span class="cx"> Note that this object will always duplicate the calendar objects when doing
</span><span class="lines">@@ -83,7 +82,10 @@
</span><span class="cx"> "DTSTAMP",
</span><span class="cx"> "LAST-MODIFIED",
</span><span class="cx"> ))
</span><del>- calendar.removeXProperties(keep_properties=config.Scheduling.CalDAV.OrganizerPublicProperties)
</del><ins>+ calendar.removeXProperties(keep_properties=(
+ config.Scheduling.CalDAV.OrganizerPublicProperties +
+ config.Scheduling.CalDAV.AttendeePublicProperties
+ ))
</ins><span class="cx"> calendar.removePropertyParameters("ATTENDEE", ("RSVP", "SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND",))
</span><span class="cx"> calendar.normalizeAll()
</span><span class="cx"> return calendar
</span><span class="lines">@@ -356,7 +358,7 @@
</span><span class="cx">
</span><span class="cx"> # If smart_merge is happening, then derive an instance in the new data as the change in the old
</span><span class="cx"> # data is valid and likely due to some other attendee changing their status.
</span><del>- if self.smart_merge:
</del><ins>+ if self.smart_merge:
</ins><span class="cx"> newOverride = self.newcalendar.deriveInstance(rid, allowCancelled=True)
</span><span class="cx"> if newOverride is None:
</span><span class="cx"> self._logDiffError("attendeeMerge: Could not derive instance for uncancelled component: %s" % (key,))
</span><span class="lines">@@ -506,7 +508,7 @@
</span><span class="cx">
</span><span class="cx"> # If PARTSTAT was changed by the attendee, add a timestamp if needed
</span><span class="cx"> if config.Scheduling.Options.TimestampAttendeePartStatChanges:
</span><del>- serverAttendee.setParameter("X-CALENDARSERVER-DTSTAMP", PyCalendarDateTime.getNowUTC().getText())
</del><ins>+ serverAttendee.setParameter(DTSTAMP_PARAM, PyCalendarDateTime.getNowUTC().getText())
</ins><span class="cx"> serverAttendee.removeParameter("X-CALENDARSERVER-AUTO")
</span><span class="cx">
</span><span class="cx"> replyNeeded = True
</span><span class="lines">@@ -527,14 +529,26 @@
</span><span class="cx"> else:
</span><span class="cx"> serverAttendee.setParameter("RSVP", "TRUE")
</span><span class="cx">
</span><ins>+ for pname in config.Scheduling.CalDAV.AttendeePublicParameters:
+ serverValue = serverAttendee.parameterValue(pname)
+ clientValue = clientAttendee.parameterValue(pname)
+ if serverValue != clientValue:
+ if clientValue is None:
+ serverAttendee.removeParameter(pname)
+ else:
+ serverAttendee.setParameter(pname, clientValue)
+ replyNeeded = True
+
</ins><span class="cx"> # Transfer these properties from the client data
</span><del>- replyNeeded |= self._transferProperty("X-CALENDARSERVER-PRIVATE-COMMENT", serverComponent, clientComponent)
</del><span class="cx"> self._transferProperty("TRANSP", serverComponent, clientComponent)
</span><span class="cx"> self._transferProperty("DTSTAMP", serverComponent, clientComponent)
</span><span class="cx"> self._transferProperty("LAST-MODIFIED", serverComponent, clientComponent)
</span><span class="cx"> self._transferProperty("COMPLETED", serverComponent, clientComponent)
</span><span class="cx"> for pname in config.Scheduling.CalDAV.PerAttendeeProperties:
</span><span class="cx"> self._transferProperty(pname, serverComponent, clientComponent)
</span><ins>+ replyNeeded |= self._transferProperty(PRIVATE_COMMENT, serverComponent, clientComponent)
+ for pname in config.Scheduling.CalDAV.AttendeePublicProperties:
+ replyNeeded |= self._transferProperty(pname, serverComponent, clientComponent)
</ins><span class="cx">
</span><span class="cx"> # Dropbox - this now never returns false
</span><span class="cx"> if config.EnableDropBox:
</span><span class="lines">@@ -621,8 +635,8 @@
</span><span class="cx"> duration = component.getProperty("DURATION")
</span><span class="cx">
</span><span class="cx"> timeRange = PyCalendarPeriod(
</span><del>- start=dtstart.value() if dtstart is not None else None,
- end=dtend.value() if dtend is not None else None,
</del><ins>+ start=dtstart.value() if dtstart is not None else None,
+ end=dtend.value() if dtend is not None else None,
</ins><span class="cx"> duration=duration.value() if duration is not None else None,
</span><span class="cx"> )
</span><span class="cx"> newdue = None
</span><span class="lines">@@ -633,7 +647,7 @@
</span><span class="cx">
</span><span class="cx"> if dtstart or duration:
</span><span class="cx"> timeRange = PyCalendarPeriod(
</span><del>- start=dtstart.value() if dtstart is not None else None,
</del><ins>+ start=dtstart.value() if dtstart is not None else None,
</ins><span class="cx"> duration=duration.value() if duration is not None else None,
</span><span class="cx"> )
</span><span class="cx"> else:
</span><span class="lines">@@ -838,7 +852,7 @@
</span><span class="cx"> "DTSTAMP",
</span><span class="cx"> "CREATED",
</span><span class="cx"> "LAST-MODIFIED",
</span><del>- "X-CALENDARSERVER-PRIVATE-COMMENT",
</del><ins>+ PRIVATE_COMMENT,
</ins><span class="cx"> ):
</span><span class="cx"> continue
</span><span class="cx"> propsChanged.setdefault(prop.name(), set())
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer54devtxdavcaldavdatastoreschedulingitippy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/itip.py (15308 => 15309)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/itip.py        2015-11-13 17:46:32 UTC (rev 15308)
+++ CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/itip.py        2015-11-13 17:49:04 UTC (rev 15309)
</span><span class="lines">@@ -31,10 +31,13 @@
</span><span class="cx">
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.ical import Property, iCalendarProductID, Component, \
</span><del>- ignoredComponents
</del><ins>+ ignoredComponents, PRIVATE_COMMENT, ATTENDEE_COMMENT, ATTENDEE_COMMENT_REF, \
+ DTSTAMP_PARAM
</ins><span class="cx">
</span><span class="cx"> from pycalendar.datetime import PyCalendarDateTime
</span><span class="cx">
</span><ins>+from collections import namedtuple
+
</ins><span class="cx"> log = Logger()
</span><span class="cx">
</span><span class="cx"> __all__ = [
</span><span class="lines">@@ -124,13 +127,13 @@
</span><span class="cx"> current_master = calendar.masterComponent()
</span><span class="cx"> if current_master:
</span><span class="cx"> valarms = [comp for comp in current_master.subcomponents() if comp.name() == "VALARM"]
</span><del>- private_comments = tuple(current_master.properties("X-CALENDARSERVER-PRIVATE-COMMENT"))
</del><ins>+ private_comments = tuple(current_master.properties(PRIVATE_COMMENT))
</ins><span class="cx"> transps = tuple(current_master.properties("TRANSP"))
</span><span class="cx"> completeds = tuple(current_master.properties("COMPLETED"))
</span><span class="cx"> organizer = current_master.getProperty("ORGANIZER")
</span><span class="cx"> organizer_schedule_status = organizer.parameterValue("SCHEDULE-STATUS", None) if organizer else None
</span><span class="cx"> attendee = current_master.getAttendeeProperty((recipient,))
</span><del>- attendee_dtstamp = attendee.parameterValue("X-CALENDARSERVER-DTSTAMP") if attendee else None
</del><ins>+ attendee_dtstamp = attendee.parameterValue(DTSTAMP_PARAM) if attendee else None
</ins><span class="cx"> other_props = {}
</span><span class="cx"> for pname in config.Scheduling.CalDAV.PerAttendeeProperties:
</span><span class="cx"> props = tuple(current_master.properties(pname))
</span><span class="lines">@@ -310,6 +313,11 @@
</span><span class="cx"> return True, False, rids
</span><span class="cx">
</span><span class="cx">
</span><ins>+ # Tuple used to hold information about what an ATTENDEE changed in their REPLY
+ # "params" indicates which parameters in the ATTENDEE property changes
+ # "props" indicates which properties changed
+ ReplyChanges = namedtuple("ReplyChanges", ("params", "props"))
+
</ins><span class="cx"> @staticmethod
</span><span class="cx"> def processReply(itip_message, calendar):
</span><span class="cx"> """
</span><span class="lines">@@ -340,13 +348,13 @@
</span><span class="cx"> old_master = calendar.masterComponent()
</span><span class="cx"> new_master = itip_message.masterComponent()
</span><span class="cx"> attendees = set()
</span><del>- rids = set()
</del><ins>+ rids = []
</ins><span class="cx"> if new_master is not None and old_master is not None:
</span><del>- attendee, partstat, private_comment = iTipProcessing.updateAttendeeData(new_master, old_master)
</del><ins>+ attendee, reply_changes = iTipProcessing.updateAttendeeData(new_master, old_master)
</ins><span class="cx"> if attendee:
</span><span class="cx"> attendees.add(attendee)
</span><del>- if partstat or private_comment:
- rids.add(("", partstat, private_comment,))
</del><ins>+ if reply_changes is not None:
+ rids.append(("", reply_changes,))
</ins><span class="cx">
</span><span class="cx"> # Make sure all overridden components in the organizer's copy have matching overridden components
</span><span class="cx"> # in the iTIP message
</span><span class="lines">@@ -392,11 +400,11 @@
</span><span class="cx"> log.error("Ignoring instance: %s in iTIP REPLY for: %s" % (rid, itip_message.resourceUID()))
</span><span class="cx"> continue
</span><span class="cx">
</span><del>- attendee, partstat, private_comment = iTipProcessing.updateAttendeeData(itip_component, match_component)
</del><ins>+ attendee, reply_changes = iTipProcessing.updateAttendeeData(itip_component, match_component)
</ins><span class="cx"> if attendee:
</span><span class="cx"> attendees.add(attendee)
</span><del>- if rids is not None and (partstat or private_comment):
- rids.add((rid.getText(), partstat, private_comment,))
</del><ins>+ if rids is not None and reply_changes is not None:
+ rids.append((rid.getText(), reply_changes,))
</ins><span class="cx">
</span><span class="cx"> # Check for an invalid instance by itself
</span><span class="cx"> len_attendees = len(attendees)
</span><span class="lines">@@ -419,11 +427,13 @@
</span><span class="cx"> @type from_component: L{Component}
</span><span class="cx"> @param to_component: component to copy to
</span><span class="cx"> @type to_component: L{Component}
</span><ins>+
+ @return: tuple of attendee property value and reply changes
+ @rtype: L{tuple} of L{str}, L{ReplyChanges}
</ins><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> # Track what changed
</span><del>- partstat_changed = False
- private_comment_changed = False
</del><ins>+ reply_changes = iTipProcessing.ReplyChanges([], [])
</ins><span class="cx">
</span><span class="cx"> # Get REQUEST-STATUS as we need to write that into the saved ATTENDEE property
</span><span class="cx"> reqstatus = tuple(from_component.properties("REQUEST-STATUS"))
</span><span class="lines">@@ -436,7 +446,7 @@
</span><span class="cx"> attendees = tuple(from_component.properties("ATTENDEE"))
</span><span class="cx"> if len(attendees) != 1:
</span><span class="cx"> log.error("There must be one and only one ATTENDEE property in a REPLY\n%s" % (str(from_component),))
</span><del>- return None, False, False
</del><ins>+ return None, None
</ins><span class="cx">
</span><span class="cx"> attendee = attendees[0]
</span><span class="cx"> partstat = attendee.parameterValue("PARTSTAT", "NEEDS-ACTION")
</span><span class="lines">@@ -444,28 +454,40 @@
</span><span class="cx"> # Now find matching ATTENDEE in to_component
</span><span class="cx"> existing_attendee = to_component.getAttendeeProperty((attendee.value(),))
</span><span class="cx"> if existing_attendee:
</span><ins>+ # Look for change to partstat
</ins><span class="cx"> oldpartstat = existing_attendee.parameterValue("PARTSTAT", "NEEDS-ACTION")
</span><span class="cx"> existing_attendee.setParameter("PARTSTAT", partstat)
</span><span class="cx"> existing_attendee.setParameter("SCHEDULE-STATUS", reqstatus)
</span><del>- partstat_changed = (oldpartstat != partstat)
</del><ins>+ if oldpartstat != partstat:
+ reply_changes.params.append("PARTSTAT")
</ins><span class="cx">
</span><del>- # Always delete RSVP on PARTSTAT change
- if partstat_changed:
</del><ins>+ # Always delete RSVP on PARTSTAT change
</ins><span class="cx"> try:
</span><span class="cx"> existing_attendee.removeParameter("RSVP")
</span><span class="cx"> except KeyError:
</span><span class="cx"> pass
</span><span class="cx">
</span><ins>+ # Look for change to X- parameters
+ for paramname in config.Scheduling.CalDAV.AttendeePublicParameters:
+ oldparam = existing_attendee.parameterValue(paramname)
+ newparam = attendee.parameterValue(paramname)
+ if oldparam != newparam:
+ if newparam is None:
+ existing_attendee.removeParameter(paramname)
+ else:
+ existing_attendee.setParameter(paramname, newparam)
+ reply_changes.params.append(paramname)
+
</ins><span class="cx"> # Handle attendee comments
</span><span class="cx"> if config.Scheduling.CalDAV.get("EnablePrivateComments", True):
</span><span class="cx"> # Look for X-CALENDARSERVER-PRIVATE-COMMENT property in iTIP component (State 1 in spec)
</span><del>- attendee_comment = tuple(from_component.properties("X-CALENDARSERVER-PRIVATE-COMMENT"))
</del><ins>+ attendee_comment = tuple(from_component.properties(PRIVATE_COMMENT))
</ins><span class="cx"> attendee_comment = attendee_comment[0] if len(attendee_comment) else None
</span><span class="cx">
</span><span class="cx"> # Look for matching X-CALENDARSERVER-ATTENDEE-COMMENT property in existing data (State 2 in spec)
</span><del>- private_comments = tuple(to_component.properties("X-CALENDARSERVER-ATTENDEE-COMMENT"))
</del><ins>+ private_comments = tuple(to_component.properties(ATTENDEE_COMMENT))
</ins><span class="cx"> for comment in private_comments:
</span><del>- attendeeref = comment.parameterValue("X-CALENDARSERVER-ATTENDEE-REF")
</del><ins>+ attendeeref = comment.parameterValue(ATTENDEE_COMMENT_REF)
</ins><span class="cx"> if attendeeref == attendee.value():
</span><span class="cx"> private_comment = comment
</span><span class="cx"> break
</span><span class="lines">@@ -484,22 +506,22 @@
</span><span class="cx"> # We now remove the private comment on the organizer's side if the attendee removed it
</span><span class="cx"> to_component.removeProperty(private_comment)
</span><span class="cx">
</span><del>- private_comment_changed = True
</del><ins>+ reply_changes.props.append(PRIVATE_COMMENT)
</ins><span class="cx">
</span><span class="cx"> elif attendee_comment is not None and private_comment is None:
</span><span class="cx">
</span><span class="cx"> # Add new property
</span><span class="cx"> private_comment = Property(
</span><del>- "X-CALENDARSERVER-ATTENDEE-COMMENT",
</del><ins>+ ATTENDEE_COMMENT,
</ins><span class="cx"> attendee_comment.value(),
</span><span class="cx"> params={
</span><del>- "X-CALENDARSERVER-ATTENDEE-REF": attendee.value(),
- "X-CALENDARSERVER-DTSTAMP": PyCalendarDateTime.getNowUTC().getText(),
</del><ins>+ ATTENDEE_COMMENT_REF: attendee.value(),
+ DTSTAMP_PARAM: PyCalendarDateTime.getNowUTC().getText(),
</ins><span class="cx"> }
</span><span class="cx"> )
</span><span class="cx"> to_component.addProperty(private_comment)
</span><span class="cx">
</span><del>- private_comment_changed = True
</del><ins>+ reply_changes.props.append(PRIVATE_COMMENT)
</ins><span class="cx">
</span><span class="cx"> else:
</span><span class="cx"> # Only change if different
</span><span class="lines">@@ -508,17 +530,31 @@
</span><span class="cx"> private_comment.removeAllParameters()
</span><span class="cx">
</span><span class="cx"> # Add default parameters
</span><del>- private_comment.setParameter("X-CALENDARSERVER-ATTENDEE-REF", attendee.value())
- private_comment.setParameter("X-CALENDARSERVER-DTSTAMP", PyCalendarDateTime.getNowUTC().getText())
</del><ins>+ private_comment.setParameter(ATTENDEE_COMMENT_REF, attendee.value())
+ private_comment.setParameter(DTSTAMP_PARAM, PyCalendarDateTime.getNowUTC().getText())
</ins><span class="cx">
</span><span class="cx"> # Set new value
</span><span class="cx"> private_comment.setValue(attendee_comment.value())
</span><span class="cx">
</span><del>- private_comment_changed = True
</del><ins>+ reply_changes.props.append(PRIVATE_COMMENT)
</ins><span class="cx">
</span><del>- return attendee.value(), partstat_changed, private_comment_changed
</del><ins>+ for propname in config.Scheduling.CalDAV.AttendeePublicProperties:
+ # Copy any property in the incoming component to the existing one.
+ # We do not currently delete anything in the existing component.
+ # We also remove all properties that match the name of the incoming one
+ # (i.e. we do not allow multi-occurring properties.
+ copy_props = tuple(from_component.properties(propname))
+ if copy_props:
+ to_component.removeProperties(propname)
+ for prop in copy_props:
+ to_component.addProperty(prop.duplicate())
+ reply_changes.props.append(propname)
</ins><span class="cx">
</span><ins>+ if len(reply_changes.props) == 0 and len(reply_changes.params) == 0:
+ reply_changes = None
+ return attendee.value(), reply_changes
</ins><span class="cx">
</span><ins>+
</ins><span class="cx"> @staticmethod
</span><span class="cx"> def transferItems(from_calendar, to_component, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee_dtstamp, other_props, recipient, remove_matched=False):
</span><span class="cx"> """
</span><span class="lines">@@ -558,13 +594,13 @@
</span><span class="cx"> matched = from_calendar.overriddenComponent(rid)
</span><span class="cx"> if matched:
</span><span class="cx"> valarms = [comp for comp in matched.subcomponents() if comp.name() == "VALARM"]
</span><del>- private_comments = tuple(matched.properties("X-CALENDARSERVER-PRIVATE-COMMENT"))
</del><ins>+ private_comments = tuple(matched.properties(PRIVATE_COMMENT))
</ins><span class="cx"> transps = tuple(matched.properties("TRANSP"))
</span><span class="cx"> completeds = tuple(matched.properties("COMPLETED"))
</span><span class="cx"> organizer = matched.getProperty("ORGANIZER")
</span><span class="cx"> organizer_schedule_status = organizer.parameterValue("SCHEDULE-STATUS", None) if organizer else None
</span><span class="cx"> attendee = matched.getAttendeeProperty((recipient,))
</span><del>- attendee_dtstamp = attendee.parameterValue("X-CALENDARSERVER-DTSTAMP") if attendee else None
</del><ins>+ attendee_dtstamp = attendee.parameterValue(DTSTAMP_PARAM) if attendee else None
</ins><span class="cx"> other_props = {}
</span><span class="cx"> for pname in config.Scheduling.CalDAV.PerAttendeeProperties:
</span><span class="cx"> props = tuple(matched.properties(pname))
</span><span class="lines">@@ -631,7 +667,7 @@
</span><span class="cx"> # into the new one. But first remove any of the stuff we want to copy from
</span><span class="cx"> # the component being copied to.
</span><span class="cx"> to_component.removeAlarms()
</span><del>- to_component.removeProperty("X-CALENDARSERVER-PRIVATE-COMMENT")
</del><ins>+ to_component.removeProperty(PRIVATE_COMMENT)
</ins><span class="cx"> to_component.removeProperty("TRANSP")
</span><span class="cx"> to_component.removeProperty("COMPLETED")
</span><span class="cx"> for propname in other_props.keys():
</span><span class="lines">@@ -653,7 +689,7 @@
</span><span class="cx"> attendee = to_component.getAttendeeProperty((recipient,))
</span><span class="cx">
</span><span class="cx"> if attendee_dtstamp and attendee:
</span><del>- attendee.setParameter("X-CALENDARSERVER-DTSTAMP", attendee_dtstamp)
</del><ins>+ attendee.setParameter(DTSTAMP_PARAM, attendee_dtstamp)
</ins><span class="cx">
</span><span class="cx"> return False
</span><span class="cx">
</span><span class="lines">@@ -928,7 +964,7 @@
</span><span class="cx"> itip.removeAlarms()
</span><span class="cx">
</span><span class="cx"> # Remove all but essential properties
</span><del>- itip.filterProperties(keep=(
</del><ins>+ keep_properties = (
</ins><span class="cx"> "UID",
</span><span class="cx"> "RECURRENCE-ID",
</span><span class="cx"> "SEQUENCE",
</span><span class="lines">@@ -942,11 +978,13 @@
</span><span class="cx"> "EXDATE",
</span><span class="cx"> "ORGANIZER",
</span><span class="cx"> "ATTENDEE",
</span><del>- "X-CALENDARSERVER-PRIVATE-COMMENT",
</del><span class="cx"> "SUMMARY",
</span><span class="cx"> "LOCATION",
</span><span class="cx"> "DESCRIPTION",
</span><del>- ))
</del><ins>+ PRIVATE_COMMENT,
+ )
+ keep_properties += tuple(config.Scheduling.CalDAV.AttendeePublicProperties)
+ itip.filterProperties(keep=keep_properties)
</ins><span class="cx">
</span><span class="cx"> # Now set each ATTENDEE's PARTSTAT to DECLINED
</span><span class="cx"> if force_decline:
</span><span class="lines">@@ -982,14 +1020,16 @@
</span><span class="cx"> # Component properties - remove all X- except for those specified
</span><span class="cx"> if not reply:
</span><span class="cx"> # Organizer properties that need to go to the Attendees
</span><del>- keep_properties = config.Scheduling.CalDAV.OrganizerPublicProperties
</del><ins>+ keep_properties = config.Scheduling.CalDAV.OrganizerPublicProperties + config.Scheduling.CalDAV.AttendeePublicProperties
+ keep_parameters = config.Scheduling.CalDAV.OrganizerPublicParameters + config.Scheduling.CalDAV.AttendeePublicParameters
</ins><span class="cx"> else:
</span><span class="cx"> # Attendee properties that need to go to the Organizer
</span><del>- keep_properties = ("X-CALENDARSERVER-PRIVATE-COMMENT",)
- itip.removeXProperties(keep_properties=keep_properties)
</del><ins>+ keep_properties = (PRIVATE_COMMENT,) + tuple(config.Scheduling.CalDAV.AttendeePublicProperties)
+ keep_parameters = config.Scheduling.CalDAV.AttendeePublicParameters
+ itip.removeXProperties(keep_properties=keep_properties, keep_parameters=keep_parameters)
</ins><span class="cx">
</span><span class="cx"> # Property Parameters
</span><del>- itip.removePropertyParameters("ATTENDEE", ("SCHEDULE-AGENT", "SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND", "X-CALENDARSERVER-DTSTAMP",))
</del><ins>+ itip.removePropertyParameters("ATTENDEE", ("SCHEDULE-AGENT", "SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND", DTSTAMP_PARAM,))
</ins><span class="cx"> itip.removePropertyParameters("ORGANIZER", ("SCHEDULE-AGENT", "SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND",))
</span><span class="cx">
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer54devtxdavcaldavdatastoreschedulingprocessingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/processing.py (15308 => 15309)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/processing.py        2015-11-13 17:46:32 UTC (rev 15308)
+++ CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/processing.py        2015-11-13 17:49:04 UTC (rev 15309)
</span><span class="lines">@@ -26,7 +26,7 @@
</span><span class="cx">
</span><span class="cx"> from twistedcaldav import customxml, caldavxml
</span><span class="cx"> from twistedcaldav.config import config
</span><del>-from twistedcaldav.ical import Property
</del><ins>+from twistedcaldav.ical import Property, DTSTAMP_PARAM
</ins><span class="cx"> from twistedcaldav.instance import InvalidOverriddenInstanceError
</span><span class="cx"> from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
</span><span class="cx"> from twistedcaldav.memcacher import Memcacher
</span><span class="lines">@@ -207,21 +207,24 @@
</span><span class="cx">
</span><span class="cx"> # Build the schedule-changes XML element
</span><span class="cx"> attendeeReplying, rids = processed
</span><del>- partstatChanged = False
</del><ins>+ refreshNeeded = False
</ins><span class="cx"> reply_details = (customxml.Attendee.fromString(attendeeReplying),)
</span><span class="cx">
</span><del>- for rid, partstatChanged, privateCommentChanged in sorted(rids):
</del><ins>+ for rid, reply_changes in sorted(rids):
</ins><span class="cx"> recurrence = []
</span><span class="cx"> if rid == "":
</span><span class="cx"> recurrence.append(customxml.Master())
</span><span class="cx"> else:
</span><span class="cx"> recurrence.append(customxml.RecurrenceID.fromString(rid))
</span><span class="cx"> changes = []
</span><del>- if partstatChanged:
- changes.append(customxml.ChangedProperty(customxml.ChangedParameter(name="PARTSTAT"), name="ATTENDEE"))
- partstatChanged = True
- if privateCommentChanged:
- changes.append(customxml.ChangedProperty(name="X-CALENDARSERVER-PRIVATE-COMMENT"))
</del><ins>+
+ for param in reply_changes.params:
+ changes.append(customxml.ChangedProperty(customxml.ChangedParameter(name=param), name="ATTENDEE"))
+ refreshNeeded = True
+
+ for prop in reply_changes.props:
+ changes.append(customxml.ChangedProperty(name=prop))
+
</ins><span class="cx"> recurrence.append(customxml.Changes(*changes))
</span><span class="cx"> reply_details += (customxml.Recurrence(*recurrence),)
</span><span class="cx">
</span><span class="lines">@@ -235,7 +238,7 @@
</span><span class="cx"> # Only update other attendees when the partstat was changed by the reply,
</span><span class="cx"> # and only if the request does not indicate we should skip attendee refresh
</span><span class="cx"> # (e.g. inbox item processing during migration from non-implicit server)
</span><del>- if partstatChanged and not self.noAttendeeRefresh:
</del><ins>+ if refreshNeeded and not self.noAttendeeRefresh:
</ins><span class="cx"> # Check limit of attendees
</span><span class="cx"> if config.Scheduling.Options.AttendeeRefreshCountLimit == 0 or len(self.recipient_calendar.getAllUniqueAttendees()) <= config.Scheduling.Options.AttendeeRefreshCountLimit:
</span><span class="cx"> yield self.queueAttendeeUpdate((attendeeReplying, organizer,))
</span><span class="lines">@@ -1130,7 +1133,7 @@
</span><span class="cx">
</span><span class="cx"> if madeChanges:
</span><span class="cx"> attendee.setParameter("X-CALENDARSERVER-AUTO", PyCalendarDateTime.getNowUTC().getText())
</span><del>- attendee.removeParameter("X-CALENDARSERVER-DTSTAMP")
</del><ins>+ attendee.removeParameter(DTSTAMP_PARAM)
</ins><span class="cx">
</span><span class="cx"> return madeChanges
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer54devtxdavcaldavdatastoreschedulingtesttest_itippy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/test/test_itip.py (15308 => 15309)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/test/test_itip.py        2015-11-13 17:46:32 UTC (rev 15308)
+++ CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/scheduling/test/test_itip.py        2015-11-13 17:49:04 UTC (rev 15309)
</span><span class="lines">@@ -21,7 +21,7 @@
</span><span class="cx"> from twisted.trial import unittest
</span><span class="cx">
</span><span class="cx"> from twistedcaldav.stdconfig import config
</span><del>-from twistedcaldav.ical import Component
</del><ins>+from twistedcaldav.ical import Component, diff_iCalStrs, normalize_iCalStr
</ins><span class="cx">
</span><span class="cx"> from txdav.caldav.datastore.scheduling.itip import iTipProcessing, iTipGenerator
</span><span class="cx">
</span><span class="lines">@@ -34,6 +34,42 @@
</span><span class="cx"> iCalendar support tests
</span><span class="cx"> """
</span><span class="cx">
</span><ins>+ def setUp(self):
+ self.patch(config.Scheduling.CalDAV, "OrganizerPublicProperties", [
+ "X-APPLE-DROPBOX",
+ "X-APPLE-STRUCTURED-LOCATION",
+ "X-TEST-ORGANIZER-PROP1",
+ "X-TEST-ORGANIZER-PROP2",
+ "X-TEST-ORGANIZER-PROP3",
+ "X-TEST-ORGANIZER-PROP4",
+ "X-TEST-ORGANIZER-PROP5",
+ ])
+
+ self.patch(config.Scheduling.CalDAV, "OrganizerPublicParameters", [
+ "X-TEST-ORGANIZER-PARAM1",
+ "X-TEST-ORGANIZER-PARAM2",
+ "X-TEST-ORGANIZER-PARAM3",
+ "X-TEST-ORGANIZER-PARAM4",
+ "X-TEST-ORGANIZER-PARAM5",
+ ])
+
+ self.patch(config.Scheduling.CalDAV, "AttendeePublicProperties", [
+ "X-TEST-ALL-PROP1",
+ "X-TEST-ALL-PROP2",
+ "X-TEST-ALL-PROP3",
+ "X-TEST-ALL-PROP4",
+ "X-TEST-ALL-PROP5",
+ ])
+
+ self.patch(config.Scheduling.CalDAV, "AttendeePublicParameters", [
+ "X-TEST-ALL-PARAM1",
+ "X-TEST-ALL-PARAM2",
+ "X-TEST-ALL-PARAM3",
+ "X-TEST-ALL-PARAM4",
+ "X-TEST-ALL-PARAM5",
+ ])
+
+
</ins><span class="cx"> def test_processRequest(self):
</span><span class="cx"> """
</span><span class="cx"> Test iTIPProcessing.processRequest works properly for various scenarios.
</span><span class="lines">@@ -1178,7 +1214,7 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><del>- True, "mailto:user1@example.com", (("", True, False),),
</del><ins>+ True, "mailto:user1@example.com", (("", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),),
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "#1.2 Simple component, accepted",
</span><span class="lines">@@ -1219,7 +1255,7 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><del>- True, "mailto:user1@example.com", (("", True, False),),
</del><ins>+ True, "mailto:user1@example.com", (("", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),),
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "#1.3 Simple component, no change",
</span><span class="lines">@@ -1327,7 +1363,10 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><del>- True, "mailto:user1@example.com", (("", True, False), ("20080801T120000Z", True, False),),
</del><ins>+ True, "mailto:user1@example.com", (
+ ("", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+ ("20080801T120000Z", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+ ),
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "#2.2 Recurring component, change master only",
</span><span class="lines">@@ -1388,7 +1427,7 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><del>- True, "mailto:user1@example.com", (("", True, False),),
</del><ins>+ True, "mailto:user1@example.com", (("", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),),
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "#2.3 Recurring component, change override only",
</span><span class="lines">@@ -1450,7 +1489,7 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><del>- True, "mailto:user1@example.com", (("20080801T120000Z", True, False),),
</del><ins>+ True, "mailto:user1@example.com", (("20080801T120000Z", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),),
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "#3.1 Recurring component, change master/override, new override",
</span><span class="lines">@@ -1532,7 +1571,11 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><del>- True, "mailto:user1@example.com", (("", True, False), ("20080801T120000Z", True, False), ("20080901T120000Z", True, False),),
</del><ins>+ True, "mailto:user1@example.com", (
+ ("", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+ ("20080801T120000Z", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+ ("20080901T120000Z", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+ ),
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "#3.2 Recurring component, change master, new override",
</span><span class="lines">@@ -1608,7 +1651,10 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><del>- True, "mailto:user1@example.com", (("", True, False), ("20080901T120000Z", True, False),),
</del><ins>+ True, "mailto:user1@example.com", (
+ ("", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+ ("20080901T120000Z", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+ ),
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "#3.3 Recurring component, change override, new override",
</span><span class="lines">@@ -1685,7 +1731,10 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><del>- True, "mailto:user1@example.com", (("20080801T120000Z", True, False), ("20080901T120000Z", True, False),),
</del><ins>+ True, "mailto:user1@example.com", (
+ ("20080801T120000Z", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+ ("20080901T120000Z", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+ ),
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "#4.1 Recurring component, invalid override",
</span><span class="lines">@@ -1882,7 +1931,7 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><del>- True, "mailto:user1@example.com", (("", True, False),),
</del><ins>+ True, "mailto:user1@example.com", (("", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),),
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "#6.2 Multiple REQUEST-STATUS",
</span><span class="lines">@@ -1925,7 +1974,7 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><del>- True, "mailto:user1@example.com", (("", True, False),),
</del><ins>+ True, "mailto:user1@example.com", (("", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),),
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "#6.3 Bad REQUEST-STATUS",
</span><span class="lines">@@ -1967,7 +2016,7 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><del>- True, "mailto:user1@example.com", (("", True, False),),
</del><ins>+ True, "mailto:user1@example.com", (("", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),),
</ins><span class="cx"> ),
</span><span class="cx"> )
</span><span class="cx">
</span><span class="lines">@@ -1975,11 +2024,6 @@
</span><span class="cx"> calendar = Component.fromString(calendar_txt)
</span><span class="cx"> itipmsg = Component.fromString(itipmsg_txt)
</span><span class="cx"> reply_success, reply_processed = iTipProcessing.processReply(itipmsg, calendar)
</span><del>-# if not description.startswith("#3.1"):
-# continue
-# print(description)
-# print(str(calendar))
-# print(str(result))
</del><span class="cx"> self.assertEqual(
</span><span class="cx"> str(calendar).replace("\r", "").replace("\n ", ""),
</span><span class="cx"> str(result).replace("\n ", ""),
</span><span class="lines">@@ -2010,6 +2054,663 @@
</span><span class="cx"> )
</span><span class="cx">
</span><span class="cx">
</span><ins>+ def test_processReply_XDash(self):
+ """
+ Test iTIPProcessing.processReply with X- property and parameter changes
+ """
+
+ data = (
+ (
+ "1.1 Simple Reply - with X- param",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071115T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE;X-TEST-ALL-PARAM1=p1;PARTSTAT=ACCEPTED:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;X-TEST-ALL-PARAM1=p1;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user02@example.com", (
+ ("", iTipProcessing.ReplyChanges(params=['PARTSTAT', 'X-TEST-ALL-PARAM1'], props=[])),
+ ),
+ ),
+ (
+ "1.2 Simple Reply - with X- param update",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;X-TEST-ALL-PARAM1=p1;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071115T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE;X-TEST-ALL-PARAM1=p1-1;PARTSTAT=ACCEPTED:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;X-TEST-ALL-PARAM1=p1-1;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user02@example.com", (
+ ("", iTipProcessing.ReplyChanges(params=['X-TEST-ALL-PARAM1'], props=[])),
+ ),
+ ),
+ (
+ "1.3 Simple Reply - with X- param remove",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;X-TEST-ALL-PARAM1=p1;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071115T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user02@example.com", (
+ ("", iTipProcessing.ReplyChanges(params=['X-TEST-ALL-PARAM1'], props=[])),
+ ),
+ ),
+ (
+ "2.1 Simple Reply - with X- prop",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071115T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02@example.com
+X-TEST-ALL-PROP1:p1
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+X-TEST-ALL-PROP1:p1
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user02@example.com", (
+ ("", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=["X-TEST-ALL-PROP1"])),
+ ),
+ ),
+ (
+ "2.2 Simple Reply - with X- prop update",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+X-TEST-ALL-PROP1:p1
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071115T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02@example.com
+X-TEST-ALL-PROP1:p1-1
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+X-TEST-ALL-PROP1:p1-1
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user02@example.com", (
+ ("", iTipProcessing.ReplyChanges(params=[], props=["X-TEST-ALL-PROP1"])),
+ ),
+ ),
+ (
+ "2.3 Simple Reply - with X- prop preserve",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+X-TEST-ALL-PROP1:p1-1
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071115T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE;PARTSTAT=DECLINED:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=DECLINED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+X-TEST-ALL-PROP1:p1-1
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user02@example.com", (
+ ("", iTipProcessing.ReplyChanges(params=["PARTSTAT"], props=[])),
+ ),
+ ),
+ (
+ "3.1 Recurrence Reply - with X- param",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE;X-TEST-ALL-PARAM1=p1;PARTSTAT=ACCEPTED:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;X-TEST-ALL-PARAM1=p1;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user02@example.com", (
+ ("20071116T000000Z", iTipProcessing.ReplyChanges(params=['PARTSTAT', 'X-TEST-ALL-PARAM1'], props=[])),
+ ),
+ ),
+ (
+ "3.2 Recurrence Reply - with X- param update",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;X-TEST-ALL-PARAM1=p1;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE;X-TEST-ALL-PARAM1=p1-1;PARTSTAT=ACCEPTED:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;X-TEST-ALL-PARAM1=p1;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;X-TEST-ALL-PARAM1=p1-1;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user02@example.com", (
+ ("20071116T000000Z", iTipProcessing.ReplyChanges(params=['X-TEST-ALL-PARAM1'], props=[])),
+ ),
+ ),
+ (
+ "3.3 Recurrence Reply - with X- param remove",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;X-TEST-ALL-PARAM1=p1;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;X-TEST-ALL-PARAM1=p1;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user02@example.com", (
+ ("20071116T000000Z", iTipProcessing.ReplyChanges(params=['X-TEST-ALL-PARAM1'], props=[])),
+ ),
+ ),
+ (
+ "4.1 Recurrence Reply - with X- prop",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02@example.com
+X-TEST-ALL-PROP1:p1
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+X-TEST-ALL-PROP1:p1
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user02@example.com", (
+ ("20071116T000000Z", iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=["X-TEST-ALL-PROP1"])),
+ ),
+ ),
+ (
+ "4.2 Recurrence Reply - with X- prop update",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+RRULE:FREQ=DAILY
+X-TEST-ALL-PROP1:p1
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02@example.com
+X-TEST-ALL-PROP1:p1-1
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+RRULE:FREQ=DAILY
+X-TEST-ALL-PROP1:p1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+X-TEST-ALL-PROP1:p1-1
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user02@example.com", (
+ ("20071116T000000Z", iTipProcessing.ReplyChanges(params=[], props=["X-TEST-ALL-PROP1"])),
+ ),
+ ),
+ (
+ "4.3 Recurrence Reply - with X- prop preserve",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+RRULE:FREQ=DAILY
+X-TEST-ALL-PROP1:p1
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE;PARTSTAT=DECLINED:mailto:user02@example.com
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+RRULE:FREQ=DAILY
+X-TEST-ALL-PROP1:p1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T000000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE;PARTSTAT=DECLINED;SCHEDULE-STATUS=2.0:mailto:user02@example.com
+X-TEST-ALL-PROP1:p1
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user02@example.com", (
+ ("20071116T000000Z", iTipProcessing.ReplyChanges(params=["PARTSTAT"], props=[])),
+ ),
+ ),
+ )
+
+ for title, calendar_txt, itip_txt, changed_txt, attendee, rids in data:
+ calendar = Component.fromString(calendar_txt)
+ itip = Component.fromString(itip_txt)
+ changed = Component.fromString(changed_txt)
+
+ result, reply_processed = iTipProcessing.processReply(itip, calendar)
+ self.assertTrue(result)
+ self.assertEqual(normalize_iCalStr(changed), normalize_iCalStr(calendar), "Calendar mismatch: {}:\n{}".format(title, diff_iCalStrs(changed, calendar),))
+ reply_attendee, reply_rids, = reply_processed
+ self.assertEqual(
+ reply_attendee,
+ attendee,
+ msg=title
+ )
+ self.assertEqual(
+ tuple(sorted(list(reply_rids), key=lambda x: x[0])),
+ rids,
+ msg=title
+ )
+
+
</ins><span class="cx"> def test_sequenceComparison(self):
</span><span class="cx"> """
</span><span class="cx"> Test iTIPProcessing.sequenceComparison
</span><span class="lines">@@ -2512,6 +3213,42 @@
</span><span class="cx"> """
</span><span class="cx"> data_dir = os.path.join(os.path.dirname(__file__), "data")
</span><span class="cx">
</span><ins>+ def setUp(self):
+ self.patch(config.Scheduling.CalDAV, "OrganizerPublicProperties", [
+ "X-APPLE-DROPBOX",
+ "X-APPLE-STRUCTURED-LOCATION",
+ "X-TEST-ORGANIZER-PROP1",
+ "X-TEST-ORGANIZER-PROP2",
+ "X-TEST-ORGANIZER-PROP3",
+ "X-TEST-ORGANIZER-PROP4",
+ "X-TEST-ORGANIZER-PROP5",
+ ])
+
+ self.patch(config.Scheduling.CalDAV, "OrganizerPublicParameters", [
+ "X-TEST-ORGANIZER-PARAM1",
+ "X-TEST-ORGANIZER-PARAM2",
+ "X-TEST-ORGANIZER-PARAM3",
+ "X-TEST-ORGANIZER-PARAM4",
+ "X-TEST-ORGANIZER-PARAM5",
+ ])
+
+ self.patch(config.Scheduling.CalDAV, "AttendeePublicProperties", [
+ "X-TEST-ALL-PROP1",
+ "X-TEST-ALL-PROP2",
+ "X-TEST-ALL-PROP3",
+ "X-TEST-ALL-PROP4",
+ "X-TEST-ALL-PROP5",
+ ])
+
+ self.patch(config.Scheduling.CalDAV, "AttendeePublicParameters", [
+ "X-TEST-ALL-PARAM1",
+ "X-TEST-ALL-PARAM2",
+ "X-TEST-ALL-PARAM3",
+ "X-TEST-ALL-PARAM4",
+ "X-TEST-ALL-PARAM5",
+ ])
+
+
</ins><span class="cx"> def test_request(self):
</span><span class="cx">
</span><span class="cx"> data = (
</span><span class="lines">@@ -3140,3 +3877,283 @@
</span><span class="cx"> itipped = str(itipped).replace("\r", "")
</span><span class="cx"> itipped = "".join([line for line in itipped.splitlines(True) if not line.startswith("DTSTAMP:")])
</span><span class="cx"> self.assertEqual(filtered, itipped)
</span><ins>+
+
+ def test_prepareSchedulingMessage(self):
+ """
+ Make sure L{iTIPGenerator.prepareSchedulingMessage} correctly filters X-
+ properties and parameters.
+ """
+
+ data = (
+ (
+ "Nothing to filter",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ ),
+ (
+ "Filter X- property",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+X-FOO:BAR
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ ),
+ (
+ "Filter X- param",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+LOCATION;X-FOO=BAR:Home
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+LOCATION:Home
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ ),
+ (
+ "Keep X- property",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+X-TEST-ORGANIZER-PROP1:organizer
+X-TEST-ALL-PROP1:all
+X-FOO:BAR
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+X-TEST-ORGANIZER-PROP1:organizer
+X-TEST-ALL-PROP1:all
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ ),
+ (
+ "Keep X- param",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+LOCATION;X-TEST-ORGANIZER-PARAM1=o;X-TEST-ALL-PARAM1=a:Home
+X-TEST-ORGANIZER-PROP1;X-TEST-ORGANIZER-PARAM2=o;X-FOO=BAR:dropped-it
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+LOCATION;X-TEST-ORGANIZER-PARAM1=o;X-TEST-ALL-PARAM1=a:Home
+X-TEST-ORGANIZER-PROP1;X-TEST-ORGANIZER-PARAM2=o;X-FOO=BAR:dropped-it
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ ),
+ (
+ "Nothing to filter - reply",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "Filter X- property - reply",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+X-TEST-ORGANIZER-PROP1:organizer
+X-FOO:BAR
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "Filter X- param - reply",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+LOCATION;X-TEST-ORGANIZER-PARAM1=o;X-FOO=BAR:Home
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+LOCATION:Home
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "Keep X- property - reply",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+X-TEST-ORGANIZER-PROP1:organizer
+X-TEST-ALL-PROP1:all
+X-FOO:BAR
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+X-TEST-ALL-PROP1:all
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "Keep X- param - reply",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+LOCATION;X-TEST-ORGANIZER-PARAM1=o;X-TEST-ALL-PARAM1=a:Home
+X-TEST-ALL-PROP1;X-TEST-ALL-PARAM2=a;X-FOO=BAR:oragnizer
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART;TZID=UTC:20071114T000000
+LOCATION;X-TEST-ALL-PARAM1=a:Home
+X-TEST-ALL-PROP1;X-TEST-ALL-PARAM2=a;X-FOO=BAR:oragnizer
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ )
+
+ for title, original, processed, reply in data:
+ component = Component.fromString(original)
+ new_component = Component.fromString(processed)
+ iTipGenerator.prepareSchedulingMessage(component, reply)
+ self.assertEqual(normalize_iCalStr(new_component), normalize_iCalStr(component), "Failed {}:\n{}".format(title, diff_iCalStrs(new_component, component),))
</ins></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer54devtxdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/sql.py (15308 => 15309)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/sql.py        2015-11-13 17:46:32 UTC (rev 15308)
+++ CalendarServer/branches/release/CalendarServer-5.4-dev/txdav/caldav/datastore/sql.py        2015-11-13 17:49:04 UTC (rev 15309)
</span><span class="lines">@@ -53,7 +53,7 @@
</span><span class="cx"> from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
</span><span class="cx"> from twistedcaldav.dateops import normalizeForIndex, datetimeMktime, \
</span><span class="cx"> pyCalendarTodatetime, parseSQLDateToPyCalendar
</span><del>-from twistedcaldav.ical import Component, InvalidICalendarDataError, Property
</del><ins>+from twistedcaldav.ical import Component, InvalidICalendarDataError, Property, ATTENDEE_COMMENT
</ins><span class="cx"> from twistedcaldav.instance import InvalidOverriddenInstanceError
</span><span class="cx"> from twistedcaldav.memcacher import Memcacher
</span><span class="cx">
</span><span class="lines">@@ -1785,7 +1785,7 @@
</span><span class="cx"> if config.Scheduling.CalDAV.get("EnablePrivateComments", True):
</span><span class="cx"> old_has_private_comments = not inserting and self.hasPrivateComment
</span><span class="cx"> new_has_private_comments = component.hasPropertyInAnyComponent((
</span><del>- "X-CALENDARSERVER-ATTENDEE-COMMENT",
</del><ins>+ ATTENDEE_COMMENT,
</ins><span class="cx"> ))
</span><span class="cx">
</span><span class="cx"> if old_has_private_comments and not new_has_private_comments and internal_state == ComponentUpdateState.NORMAL:
</span><span class="lines">@@ -1793,7 +1793,7 @@
</span><span class="cx"> log.debug("Organizer private comment properties were entirely removed by the client. Restoring existing properties.")
</span><span class="cx"> old_calendar = (yield self.componentForUser())
</span><span class="cx"> component.transferProperties(old_calendar, (
</span><del>- "X-CALENDARSERVER-ATTENDEE-COMMENT",
</del><ins>+ ATTENDEE_COMMENT,
</ins><span class="cx"> ))
</span><span class="cx">
</span><span class="cx"> self.hasPrivateComment = new_has_private_comments
</span><span class="lines">@@ -1803,7 +1803,7 @@
</span><span class="cx">
</span><span class="cx"> # Look for properties with duplicate "X-CALENDARSERVER-ATTENDEE-REF" values in the same component
</span><span class="cx"> if component.hasDuplicatePrivateComments(doFix=config.RemoveDuplicatePrivateComments) and internal_state == ComponentUpdateState.NORMAL:
</span><del>- raise DuplicatePrivateCommentsError("Duplicate X-CALENDARSERVER-ATTENDEE-COMMENT properties present.")
</del><ins>+ raise DuplicatePrivateCommentsError("Duplicate {} properties present.".format(ATTENDEE_COMMENT))
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span></span></pre>
</div>
</div>
</body>
</html>