<!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">         &lt;true/&gt;
</span><span class="cx">         &lt;key&gt;EnablePrivateComments&lt;/key&gt;
</span><span class="cx">         &lt;true/&gt;
</span><ins>+        &lt;key&gt;OrganizerPublicProperties&lt;/key&gt;
+        &lt;array&gt;
+                &lt;string&gt;X-APPLE-DROPBOX&lt;/string&gt;
+                &lt;string&gt;X-APPLE-STRUCTURED-LOCATION&lt;/string&gt;
+                &lt;string&gt;X-TEST-ORGANIZER-PROP1&lt;/string&gt;
+                &lt;string&gt;X-TEST-ORGANIZER-PROP2&lt;/string&gt;
+                &lt;string&gt;X-TEST-ORGANIZER-PROP3&lt;/string&gt;
+                &lt;string&gt;X-TEST-ORGANIZER-PROP4&lt;/string&gt;
+                &lt;string&gt;X-TEST-ORGANIZER-PROP5&lt;/string&gt;
+        &lt;/array&gt;
+        &lt;key&gt;OrganizerPublicParameters&lt;/key&gt;
+        &lt;array&gt;
+                &lt;string&gt;X-TEST-ORGANIZER-PARAM1&lt;/string&gt;
+                &lt;string&gt;X-TEST-ORGANIZER-PARAM2&lt;/string&gt;
+                &lt;string&gt;X-TEST-ORGANIZER-PARAM3&lt;/string&gt;
+                &lt;string&gt;X-TEST-ORGANIZER-PARAM4&lt;/string&gt;
+                &lt;string&gt;X-TEST-ORGANIZER-PARAM5&lt;/string&gt;
+        &lt;/array&gt;
+        &lt;key&gt;AttendeePublicProperties&lt;/key&gt;
+        &lt;array&gt;
+                &lt;string&gt;X-TEST-ALL-PROP1&lt;/string&gt;
+                &lt;string&gt;X-TEST-ALL-PROP2&lt;/string&gt;
+                &lt;string&gt;X-TEST-ALL-PROP3&lt;/string&gt;
+                &lt;string&gt;X-TEST-ALL-PROP4&lt;/string&gt;
+                &lt;string&gt;X-TEST-ALL-PROP5&lt;/string&gt;
+        &lt;/array&gt;
+        &lt;key&gt;AttendeePublicParameters&lt;/key&gt;
+        &lt;array&gt;
+                &lt;string&gt;X-TEST-ALL-PARAM1&lt;/string&gt;
+                &lt;string&gt;X-TEST-ALL-PARAM2&lt;/string&gt;
+                &lt;string&gt;X-TEST-ALL-PARAM3&lt;/string&gt;
+                &lt;string&gt;X-TEST-ALL-PARAM4&lt;/string&gt;
+                &lt;string&gt;X-TEST-ALL-PARAM5&lt;/string&gt;
+        &lt;/array&gt;
</ins><span class="cx">       &lt;/dict&gt;
</span><span class="cx"> 
</span><span class="cx">       &lt;!-- iSchedule protocol options --&gt;
</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 = &quot;X-CALENDARSERVER-PERUSER-UID&quot;
</span><span class="cx"> PERINSTANCE_COMPONENT = &quot;X-CALENDARSERVER-PERINSTANCE&quot;
</span><span class="cx"> 
</span><ins>+PRIVATE_COMMENT = &quot;X-CALENDARSERVER-PRIVATE-COMMENT&quot;
+ATTENDEE_COMMENT = &quot;X-CALENDARSERVER-ATTENDEE-COMMENT&quot;
+ATTENDEE_COMMENT_REF = &quot;X-CALENDARSERVER-ATTENDEE-REF&quot;
+DTSTAMP_PARAM = &quot;X-CALENDARSERVER-DTSTAMP&quot;
+
</ins><span class="cx"> # 2445 default values and parameters
</span><span class="cx"> # Structure: propname: (&lt;default value&gt;, &lt;parameter defaults dict&gt;)
</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):
+        &quot;&quot;&quot;
+        remove all properties with name
+        @param name: the name of the properties to remove.
+        &quot;&quot;&quot;
+        self._pycalendar.removeProperties(name)
+        self._pycalendar.finalise()
+        self._markAsDirty()
+
+
</ins><span class="cx">     def removeAllPropertiesWithName(self, pname):
</span><span class="cx">         &quot;&quot;&quot;
</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">         &quot;&quot;&quot;
</span><span class="cx">         Remove all X- properties except the specified ones
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         if do_subcomponents and self.name() == &quot;VCALENDAR&quot;:
</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(&quot;X-&quot;)
-                if xpname and p.name() not in keep_properties:
</del><ins>+                pname = p.name()
+                xpname = pname.startswith(&quot;X-&quot;)
+                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(&quot;X-&quot;):
</del><ins>+                        if paramname.startswith(&quot;X-&quot;) 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(&quot;X-CALENDARSERVER-ATTENDEE-COMMENT&quot;)):
-                ref = prop.parameterValue(&quot;X-CALENDARSERVER-ATTENDEE-REF&quot;)
</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">                 &quot;X-APPLE-DROPBOX&quot;,
</span><span class="cx">                 &quot;X-APPLE-STRUCTURED-LOCATION&quot;,
</span><span class="cx">             ],
</span><ins>+            &quot;OrganizerPublicParameters&quot;  : [     # Names of X- iCalendar parameters that are sent from ORGANIZER to ATTENDEE
+            ],
+            &quot;AttendeePublicProperties&quot;  : [     # Names of X- iCalendar properties that are sent from ATTENDEE to ORGANIZER
+                                                # These are also implicitly added to OrganizerPublicProperties
+            ],
+            &quot;AttendeePublicParameters&quot;  : [     # 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">         &quot;iSchedule&quot;: {
</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">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-
</del><span class="cx">     def __init__(self, oldcalendar, newcalendar, smart_merge, forceTRANSP=False):
</span><span class="cx">         &quot;&quot;&quot;
</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">                 &quot;DTSTAMP&quot;,
</span><span class="cx">                 &quot;LAST-MODIFIED&quot;,
</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(&quot;ATTENDEE&quot;, (&quot;RSVP&quot;, &quot;SCHEDULE-STATUS&quot;, &quot;SCHEDULE-FORCE-SEND&quot;,))
</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(&quot;attendeeMerge: Could not derive instance for uncancelled component: %s&quot; % (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(&quot;X-CALENDARSERVER-DTSTAMP&quot;, PyCalendarDateTime.getNowUTC().getText())
</del><ins>+                serverAttendee.setParameter(DTSTAMP_PARAM, PyCalendarDateTime.getNowUTC().getText())
</ins><span class="cx">             serverAttendee.removeParameter(&quot;X-CALENDARSERVER-AUTO&quot;)
</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(&quot;RSVP&quot;, &quot;TRUE&quot;)
</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(&quot;X-CALENDARSERVER-PRIVATE-COMMENT&quot;, serverComponent, clientComponent)
</del><span class="cx">         self._transferProperty(&quot;TRANSP&quot;, serverComponent, clientComponent)
</span><span class="cx">         self._transferProperty(&quot;DTSTAMP&quot;, serverComponent, clientComponent)
</span><span class="cx">         self._transferProperty(&quot;LAST-MODIFIED&quot;, serverComponent, clientComponent)
</span><span class="cx">         self._transferProperty(&quot;COMPLETED&quot;, 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(&quot;DURATION&quot;)
</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">                 &quot;DTSTAMP&quot;,
</span><span class="cx">                 &quot;CREATED&quot;,
</span><span class="cx">                 &quot;LAST-MODIFIED&quot;,
</span><del>-                &quot;X-CALENDARSERVER-PRIVATE-COMMENT&quot;,
</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() == &quot;VALARM&quot;]
</span><del>-            private_comments = tuple(current_master.properties(&quot;X-CALENDARSERVER-PRIVATE-COMMENT&quot;))
</del><ins>+            private_comments = tuple(current_master.properties(PRIVATE_COMMENT))
</ins><span class="cx">             transps = tuple(current_master.properties(&quot;TRANSP&quot;))
</span><span class="cx">             completeds = tuple(current_master.properties(&quot;COMPLETED&quot;))
</span><span class="cx">             organizer = current_master.getProperty(&quot;ORGANIZER&quot;)
</span><span class="cx">             organizer_schedule_status = organizer.parameterValue(&quot;SCHEDULE-STATUS&quot;, None) if organizer else None
</span><span class="cx">             attendee = current_master.getAttendeeProperty((recipient,))
</span><del>-            attendee_dtstamp = attendee.parameterValue(&quot;X-CALENDARSERVER-DTSTAMP&quot;) 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
+    # &quot;params&quot; indicates which parameters in the ATTENDEE property changes
+    # &quot;props&quot; indicates which properties changed
+    ReplyChanges = namedtuple(&quot;ReplyChanges&quot;, (&quot;params&quot;, &quot;props&quot;))
+
</ins><span class="cx">     @staticmethod
</span><span class="cx">     def processReply(itip_message, calendar):
</span><span class="cx">         &quot;&quot;&quot;
</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((&quot;&quot;, partstat, private_comment,))
</del><ins>+                if reply_changes is not None:
+                    rids.append((&quot;&quot;, 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(&quot;Ignoring instance: %s in iTIP REPLY for: %s&quot; % (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">         &quot;&quot;&quot;
</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(&quot;REQUEST-STATUS&quot;))
</span><span class="lines">@@ -436,7 +446,7 @@
</span><span class="cx">         attendees = tuple(from_component.properties(&quot;ATTENDEE&quot;))
</span><span class="cx">         if len(attendees) != 1:
</span><span class="cx">             log.error(&quot;There must be one and only one ATTENDEE property in a REPLY\n%s&quot; % (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(&quot;PARTSTAT&quot;, &quot;NEEDS-ACTION&quot;)
</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(&quot;PARTSTAT&quot;, &quot;NEEDS-ACTION&quot;)
</span><span class="cx">             existing_attendee.setParameter(&quot;PARTSTAT&quot;, partstat)
</span><span class="cx">             existing_attendee.setParameter(&quot;SCHEDULE-STATUS&quot;, reqstatus)
</span><del>-            partstat_changed = (oldpartstat != partstat)
</del><ins>+            if oldpartstat != partstat:
+                reply_changes.params.append(&quot;PARTSTAT&quot;)
</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(&quot;RSVP&quot;)
</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(&quot;EnablePrivateComments&quot;, 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(&quot;X-CALENDARSERVER-PRIVATE-COMMENT&quot;))
</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(&quot;X-CALENDARSERVER-ATTENDEE-COMMENT&quot;))
</del><ins>+                private_comments = tuple(to_component.properties(ATTENDEE_COMMENT))
</ins><span class="cx">                 for comment in private_comments:
</span><del>-                    attendeeref = comment.parameterValue(&quot;X-CALENDARSERVER-ATTENDEE-REF&quot;)
</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>-                    &quot;X-CALENDARSERVER-ATTENDEE-COMMENT&quot;,
</del><ins>+                    ATTENDEE_COMMENT,
</ins><span class="cx">                     attendee_comment.value(),
</span><span class="cx">                     params={
</span><del>-                        &quot;X-CALENDARSERVER-ATTENDEE-REF&quot;: attendee.value(),
-                        &quot;X-CALENDARSERVER-DTSTAMP&quot;: 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(&quot;X-CALENDARSERVER-ATTENDEE-REF&quot;, attendee.value())
-                    private_comment.setParameter(&quot;X-CALENDARSERVER-DTSTAMP&quot;, 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">         &quot;&quot;&quot;
</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() == &quot;VALARM&quot;]
</span><del>-            private_comments = tuple(matched.properties(&quot;X-CALENDARSERVER-PRIVATE-COMMENT&quot;))
</del><ins>+            private_comments = tuple(matched.properties(PRIVATE_COMMENT))
</ins><span class="cx">             transps = tuple(matched.properties(&quot;TRANSP&quot;))
</span><span class="cx">             completeds = tuple(matched.properties(&quot;COMPLETED&quot;))
</span><span class="cx">             organizer = matched.getProperty(&quot;ORGANIZER&quot;)
</span><span class="cx">             organizer_schedule_status = organizer.parameterValue(&quot;SCHEDULE-STATUS&quot;, None) if organizer else None
</span><span class="cx">             attendee = matched.getAttendeeProperty((recipient,))
</span><del>-            attendee_dtstamp = attendee.parameterValue(&quot;X-CALENDARSERVER-DTSTAMP&quot;) 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(&quot;X-CALENDARSERVER-PRIVATE-COMMENT&quot;)
</del><ins>+        to_component.removeProperty(PRIVATE_COMMENT)
</ins><span class="cx">         to_component.removeProperty(&quot;TRANSP&quot;)
</span><span class="cx">         to_component.removeProperty(&quot;COMPLETED&quot;)
</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(&quot;X-CALENDARSERVER-DTSTAMP&quot;, 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">             &quot;UID&quot;,
</span><span class="cx">             &quot;RECURRENCE-ID&quot;,
</span><span class="cx">             &quot;SEQUENCE&quot;,
</span><span class="lines">@@ -942,11 +978,13 @@
</span><span class="cx">             &quot;EXDATE&quot;,
</span><span class="cx">             &quot;ORGANIZER&quot;,
</span><span class="cx">             &quot;ATTENDEE&quot;,
</span><del>-            &quot;X-CALENDARSERVER-PRIVATE-COMMENT&quot;,
</del><span class="cx">             &quot;SUMMARY&quot;,
</span><span class="cx">             &quot;LOCATION&quot;,
</span><span class="cx">             &quot;DESCRIPTION&quot;,
</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 = (&quot;X-CALENDARSERVER-PRIVATE-COMMENT&quot;,)
-        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(&quot;ATTENDEE&quot;, (&quot;SCHEDULE-AGENT&quot;, &quot;SCHEDULE-STATUS&quot;, &quot;SCHEDULE-FORCE-SEND&quot;, &quot;X-CALENDARSERVER-DTSTAMP&quot;,))
</del><ins>+        itip.removePropertyParameters(&quot;ATTENDEE&quot;, (&quot;SCHEDULE-AGENT&quot;, &quot;SCHEDULE-STATUS&quot;, &quot;SCHEDULE-FORCE-SEND&quot;, DTSTAMP_PARAM,))
</ins><span class="cx">         itip.removePropertyParameters(&quot;ORGANIZER&quot;, (&quot;SCHEDULE-AGENT&quot;, &quot;SCHEDULE-STATUS&quot;, &quot;SCHEDULE-FORCE-SEND&quot;,))
</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 == &quot;&quot;:
</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=&quot;PARTSTAT&quot;), name=&quot;ATTENDEE&quot;))
-                    partstatChanged = True
-                if privateCommentChanged:
-                    changes.append(customxml.ChangedProperty(name=&quot;X-CALENDARSERVER-PRIVATE-COMMENT&quot;))
</del><ins>+
+                for param in reply_changes.params:
+                    changes.append(customxml.ChangedProperty(customxml.ChangedParameter(name=param), name=&quot;ATTENDEE&quot;))
+                    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()) &lt;= 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(&quot;X-CALENDARSERVER-AUTO&quot;, PyCalendarDateTime.getNowUTC().getText())
</span><del>-                attendee.removeParameter(&quot;X-CALENDARSERVER-DTSTAMP&quot;)
</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">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+    def setUp(self):
+        self.patch(config.Scheduling.CalDAV, &quot;OrganizerPublicProperties&quot;, [
+            &quot;X-APPLE-DROPBOX&quot;,
+            &quot;X-APPLE-STRUCTURED-LOCATION&quot;,
+            &quot;X-TEST-ORGANIZER-PROP1&quot;,
+            &quot;X-TEST-ORGANIZER-PROP2&quot;,
+            &quot;X-TEST-ORGANIZER-PROP3&quot;,
+            &quot;X-TEST-ORGANIZER-PROP4&quot;,
+            &quot;X-TEST-ORGANIZER-PROP5&quot;,
+        ])
+
+        self.patch(config.Scheduling.CalDAV, &quot;OrganizerPublicParameters&quot;, [
+            &quot;X-TEST-ORGANIZER-PARAM1&quot;,
+            &quot;X-TEST-ORGANIZER-PARAM2&quot;,
+            &quot;X-TEST-ORGANIZER-PARAM3&quot;,
+            &quot;X-TEST-ORGANIZER-PARAM4&quot;,
+            &quot;X-TEST-ORGANIZER-PARAM5&quot;,
+        ])
+
+        self.patch(config.Scheduling.CalDAV, &quot;AttendeePublicProperties&quot;, [
+            &quot;X-TEST-ALL-PROP1&quot;,
+            &quot;X-TEST-ALL-PROP2&quot;,
+            &quot;X-TEST-ALL-PROP3&quot;,
+            &quot;X-TEST-ALL-PROP4&quot;,
+            &quot;X-TEST-ALL-PROP5&quot;,
+        ])
+
+        self.patch(config.Scheduling.CalDAV, &quot;AttendeePublicParameters&quot;, [
+            &quot;X-TEST-ALL-PARAM1&quot;,
+            &quot;X-TEST-ALL-PARAM2&quot;,
+            &quot;X-TEST-ALL-PARAM3&quot;,
+            &quot;X-TEST-ALL-PARAM4&quot;,
+            &quot;X-TEST-ALL-PARAM5&quot;,
+        ])
+
+
</ins><span class="cx">     def test_processRequest(self):
</span><span class="cx">         &quot;&quot;&quot;
</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"> &quot;&quot;&quot;,
</span><del>-                True, &quot;mailto:user1@example.com&quot;, ((&quot;&quot;, True, False),),
</del><ins>+                True, &quot;mailto:user1@example.com&quot;, ((&quot;&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),),
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;#1.2 Simple component, accepted&quot;,
</span><span class="lines">@@ -1219,7 +1255,7 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><del>-                True, &quot;mailto:user1@example.com&quot;, ((&quot;&quot;, True, False),),
</del><ins>+                True, &quot;mailto:user1@example.com&quot;, ((&quot;&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),),
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;#1.3 Simple component, no change&quot;,
</span><span class="lines">@@ -1327,7 +1363,10 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><del>-                True, &quot;mailto:user1@example.com&quot;, ((&quot;&quot;, True, False), (&quot;20080801T120000Z&quot;, True, False),),
</del><ins>+                True, &quot;mailto:user1@example.com&quot;, (
+                    (&quot;&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+                    (&quot;20080801T120000Z&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+                ),
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;#2.2 Recurring component, change master only&quot;,
</span><span class="lines">@@ -1388,7 +1427,7 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><del>-                True, &quot;mailto:user1@example.com&quot;, ((&quot;&quot;, True, False),),
</del><ins>+                True, &quot;mailto:user1@example.com&quot;, ((&quot;&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),),
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;#2.3 Recurring component, change override only&quot;,
</span><span class="lines">@@ -1450,7 +1489,7 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><del>-                True, &quot;mailto:user1@example.com&quot;, ((&quot;20080801T120000Z&quot;, True, False),),
</del><ins>+                True, &quot;mailto:user1@example.com&quot;, ((&quot;20080801T120000Z&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),),
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;#3.1 Recurring component, change master/override, new override&quot;,
</span><span class="lines">@@ -1532,7 +1571,11 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><del>-                True, &quot;mailto:user1@example.com&quot;, ((&quot;&quot;, True, False), (&quot;20080801T120000Z&quot;, True, False), (&quot;20080901T120000Z&quot;, True, False),),
</del><ins>+                True, &quot;mailto:user1@example.com&quot;, (
+                    (&quot;&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+                    (&quot;20080801T120000Z&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+                    (&quot;20080901T120000Z&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+                ),
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;#3.2 Recurring component, change master, new override&quot;,
</span><span class="lines">@@ -1608,7 +1651,10 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><del>-                True, &quot;mailto:user1@example.com&quot;, ((&quot;&quot;, True, False), (&quot;20080901T120000Z&quot;, True, False),),
</del><ins>+                True, &quot;mailto:user1@example.com&quot;, (
+                    (&quot;&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+                    (&quot;20080901T120000Z&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+                ),
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;#3.3 Recurring component, change override, new override&quot;,
</span><span class="lines">@@ -1685,7 +1731,10 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><del>-                True, &quot;mailto:user1@example.com&quot;, ((&quot;20080801T120000Z&quot;, True, False), (&quot;20080901T120000Z&quot;, True, False),),
</del><ins>+                True, &quot;mailto:user1@example.com&quot;, (
+                    (&quot;20080801T120000Z&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+                    (&quot;20080901T120000Z&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),
+                ),
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;#4.1 Recurring component, invalid override&quot;,
</span><span class="lines">@@ -1882,7 +1931,7 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><del>-                True, &quot;mailto:user1@example.com&quot;, ((&quot;&quot;, True, False),),
</del><ins>+                True, &quot;mailto:user1@example.com&quot;, ((&quot;&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),),
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;#6.2 Multiple REQUEST-STATUS&quot;,
</span><span class="lines">@@ -1925,7 +1974,7 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><del>-                True, &quot;mailto:user1@example.com&quot;, ((&quot;&quot;, True, False),),
</del><ins>+                True, &quot;mailto:user1@example.com&quot;, ((&quot;&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[])),),
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;#6.3 Bad REQUEST-STATUS&quot;,
</span><span class="lines">@@ -1967,7 +2016,7 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><del>-                True, &quot;mailto:user1@example.com&quot;, ((&quot;&quot;, True, False),),
</del><ins>+                True, &quot;mailto:user1@example.com&quot;, ((&quot;&quot;, 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(&quot;#3.1&quot;):
-#                continue
-#            print(description)
-#            print(str(calendar))
-#            print(str(result))
</del><span class="cx">             self.assertEqual(
</span><span class="cx">                 str(calendar).replace(&quot;\r&quot;, &quot;&quot;).replace(&quot;\n &quot;, &quot;&quot;),
</span><span class="cx">                 str(result).replace(&quot;\n &quot;, &quot;&quot;),
</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):
+        &quot;&quot;&quot;
+        Test iTIPProcessing.processReply with X- property and parameter changes
+        &quot;&quot;&quot;
+
+        data = (
+            (
+                &quot;1.1 Simple Reply - with X- param&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;mailto:user02@example.com&quot;, (
+                    (&quot;&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT', 'X-TEST-ALL-PARAM1'], props=[])),
+                ),
+            ),
+            (
+                &quot;1.2 Simple Reply - with X- param update&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;mailto:user02@example.com&quot;, (
+                    (&quot;&quot;, iTipProcessing.ReplyChanges(params=['X-TEST-ALL-PARAM1'], props=[])),
+                ),
+            ),
+            (
+                &quot;1.3 Simple Reply - with X- param remove&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;mailto:user02@example.com&quot;, (
+                    (&quot;&quot;, iTipProcessing.ReplyChanges(params=['X-TEST-ALL-PARAM1'], props=[])),
+                ),
+            ),
+            (
+                &quot;2.1 Simple Reply - with X- prop&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;mailto:user02@example.com&quot;, (
+                    (&quot;&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[&quot;X-TEST-ALL-PROP1&quot;])),
+                ),
+            ),
+            (
+                &quot;2.2 Simple Reply - with X- prop update&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;mailto:user02@example.com&quot;, (
+                    (&quot;&quot;, iTipProcessing.ReplyChanges(params=[], props=[&quot;X-TEST-ALL-PROP1&quot;])),
+                ),
+            ),
+            (
+                &quot;2.3 Simple Reply - with X- prop preserve&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;mailto:user02@example.com&quot;, (
+                    (&quot;&quot;, iTipProcessing.ReplyChanges(params=[&quot;PARTSTAT&quot;], props=[])),
+                ),
+            ),
+            (
+                &quot;3.1 Recurrence Reply - with X- param&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;mailto:user02@example.com&quot;, (
+                    (&quot;20071116T000000Z&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT', 'X-TEST-ALL-PARAM1'], props=[])),
+                ),
+            ),
+            (
+                &quot;3.2 Recurrence Reply - with X- param update&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;mailto:user02@example.com&quot;, (
+                    (&quot;20071116T000000Z&quot;, iTipProcessing.ReplyChanges(params=['X-TEST-ALL-PARAM1'], props=[])),
+                ),
+            ),
+            (
+                &quot;3.3 Recurrence Reply - with X- param remove&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;mailto:user02@example.com&quot;, (
+                    (&quot;20071116T000000Z&quot;, iTipProcessing.ReplyChanges(params=['X-TEST-ALL-PARAM1'], props=[])),
+                ),
+            ),
+            (
+                &quot;4.1 Recurrence Reply - with X- prop&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;mailto:user02@example.com&quot;, (
+                    (&quot;20071116T000000Z&quot;, iTipProcessing.ReplyChanges(params=['PARTSTAT'], props=[&quot;X-TEST-ALL-PROP1&quot;])),
+                ),
+            ),
+            (
+                &quot;4.2 Recurrence Reply - with X- prop update&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;mailto:user02@example.com&quot;, (
+                    (&quot;20071116T000000Z&quot;, iTipProcessing.ReplyChanges(params=[], props=[&quot;X-TEST-ALL-PROP1&quot;])),
+                ),
+            ),
+            (
+                &quot;4.3 Recurrence Reply - with X- prop preserve&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;mailto:user02@example.com&quot;, (
+                    (&quot;20071116T000000Z&quot;, iTipProcessing.ReplyChanges(params=[&quot;PARTSTAT&quot;], 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), &quot;Calendar mismatch: {}:\n{}&quot;.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">         &quot;&quot;&quot;
</span><span class="cx">         Test iTIPProcessing.sequenceComparison
</span><span class="lines">@@ -2512,6 +3213,42 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     data_dir = os.path.join(os.path.dirname(__file__), &quot;data&quot;)
</span><span class="cx"> 
</span><ins>+    def setUp(self):
+        self.patch(config.Scheduling.CalDAV, &quot;OrganizerPublicProperties&quot;, [
+            &quot;X-APPLE-DROPBOX&quot;,
+            &quot;X-APPLE-STRUCTURED-LOCATION&quot;,
+            &quot;X-TEST-ORGANIZER-PROP1&quot;,
+            &quot;X-TEST-ORGANIZER-PROP2&quot;,
+            &quot;X-TEST-ORGANIZER-PROP3&quot;,
+            &quot;X-TEST-ORGANIZER-PROP4&quot;,
+            &quot;X-TEST-ORGANIZER-PROP5&quot;,
+        ])
+
+        self.patch(config.Scheduling.CalDAV, &quot;OrganizerPublicParameters&quot;, [
+            &quot;X-TEST-ORGANIZER-PARAM1&quot;,
+            &quot;X-TEST-ORGANIZER-PARAM2&quot;,
+            &quot;X-TEST-ORGANIZER-PARAM3&quot;,
+            &quot;X-TEST-ORGANIZER-PARAM4&quot;,
+            &quot;X-TEST-ORGANIZER-PARAM5&quot;,
+        ])
+
+        self.patch(config.Scheduling.CalDAV, &quot;AttendeePublicProperties&quot;, [
+            &quot;X-TEST-ALL-PROP1&quot;,
+            &quot;X-TEST-ALL-PROP2&quot;,
+            &quot;X-TEST-ALL-PROP3&quot;,
+            &quot;X-TEST-ALL-PROP4&quot;,
+            &quot;X-TEST-ALL-PROP5&quot;,
+        ])
+
+        self.patch(config.Scheduling.CalDAV, &quot;AttendeePublicParameters&quot;, [
+            &quot;X-TEST-ALL-PARAM1&quot;,
+            &quot;X-TEST-ALL-PARAM2&quot;,
+            &quot;X-TEST-ALL-PARAM3&quot;,
+            &quot;X-TEST-ALL-PARAM4&quot;,
+            &quot;X-TEST-ALL-PARAM5&quot;,
+        ])
+
+
</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(&quot;\r&quot;, &quot;&quot;)
</span><span class="cx">         itipped = &quot;&quot;.join([line for line in itipped.splitlines(True) if not line.startswith(&quot;DTSTAMP:&quot;)])
</span><span class="cx">         self.assertEqual(filtered, itipped)
</span><ins>+
+
+    def test_prepareSchedulingMessage(self):
+        &quot;&quot;&quot;
+        Make sure L{iTIPGenerator.prepareSchedulingMessage} correctly filters X-
+        properties and parameters.
+        &quot;&quot;&quot;
+
+        data = (
+            (
+                &quot;Nothing to filter&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                False,
+            ),
+            (
+                &quot;Filter X- property&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                False,
+            ),
+            (
+                &quot;Filter X- param&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                False,
+            ),
+            (
+                &quot;Keep X- property&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                False,
+            ),
+            (
+                &quot;Keep X- param&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                False,
+            ),
+            (
+                &quot;Nothing to filter - reply&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                True,
+            ),
+            (
+                &quot;Filter X- property - reply&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                True,
+            ),
+            (
+                &quot;Filter X- param - reply&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                True,
+            ),
+            (
+                &quot;Keep X- property - reply&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                True,
+            ),
+            (
+                &quot;Keep X- param - reply&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;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
+&quot;&quot;&quot;,
+                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), &quot;Failed {}:\n{}&quot;.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(&quot;EnablePrivateComments&quot;, 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>-                &quot;X-CALENDARSERVER-ATTENDEE-COMMENT&quot;,
</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(&quot;Organizer private comment properties were entirely removed by the client. Restoring existing properties.&quot;)
</span><span class="cx">                 old_calendar = (yield self.componentForUser())
</span><span class="cx">                 component.transferProperties(old_calendar, (
</span><del>-                    &quot;X-CALENDARSERVER-ATTENDEE-COMMENT&quot;,
</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 &quot;X-CALENDARSERVER-ATTENDEE-REF&quot; values in the same component
</span><span class="cx">             if component.hasDuplicatePrivateComments(doFix=config.RemoveDuplicatePrivateComments) and internal_state == ComponentUpdateState.NORMAL:
</span><del>-                raise DuplicatePrivateCommentsError(&quot;Duplicate X-CALENDARSERVER-ATTENDEE-COMMENT properties present.&quot;)
</del><ins>+                raise DuplicatePrivateCommentsError(&quot;Duplicate {} properties present.&quot;.format(ATTENDEE_COMMENT))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span></span></pre>
</div>
</div>

</body>
</html>