[CalendarServer-changes] [9208] CalendarServer/trunk/calendarserver/tools

source_changes at macosforge.org source_changes at macosforge.org
Thu Apr 26 19:38:33 PDT 2012


Revision: 9208
          http://trac.macosforge.org/projects/calendarserver/changeset/9208
Author:   cdaboo at apple.com
Date:     2012-04-26 19:38:33 -0700 (Thu, 26 Apr 2012)
Log Message:
-----------
Handle corrupt CS-OLD-CUA parameter. Display version number. Also scan/fix inbox data. More output tweaks.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tools/calverify.py
    CalendarServer/trunk/calendarserver/tools/test/test_calverify.py

Modified: CalendarServer/trunk/calendarserver/tools/calverify.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/calverify.py	2012-04-27 02:35:54 UTC (rev 9207)
+++ CalendarServer/trunk/calendarserver/tools/calverify.py	2012-04-27 02:38:33 UTC (rev 9208)
@@ -59,11 +59,15 @@
 from twistedcaldav.util import normalizationLookup
 from txdav.common.datastore.sql_tables import schema, _BIND_MODE_OWN
 from txdav.common.icommondatastore import InternalDataStoreError
+import base64
 import collections
 import os
 import sys
 import time
+import traceback
 
+VERSION = "2"
+
 def usage(e=None):
     if e:
         print e
@@ -78,14 +82,15 @@
         sys.exit(0)
 
 
-description = '\n'.join(
+description = ''.join(
     wordWrap(
         """
-        Usage: calendarserver_verify_data [options] [input specifiers]\n
+        Usage: calendarserver_verify_data [options] [input specifiers]
         """,
         int(os.environ.get('COLUMNS', '80'))
     )
 )
+description += "\nVersion: %s" % (VERSION,)
 
 
 def safePercent(x, y, multiplier=100.0):
@@ -101,6 +106,8 @@
 
     optFlags = [
         ['ical', 'i', "Calendar data check."],
+        ['badcua', 'i', "Calendar data check for bad CALENDARSERVER-OLD-CUA only."],
+        ['nobase64', 'n', "Do not apply CALENDARSERVER-OLD-CUA base64 transform when fixing."],
         ['mismatch', 's', "Detect organizer/attendee mismatches."],
         ['missing', 'm', "Show 'orphaned' homes."],
         ['fix', 'x', "Fix problems."],
@@ -161,6 +168,8 @@
         self.results = {}
         self.summary = []
         self.total = 0
+        self.totalErrors = None
+        self.totalExceptions = None
 
 
     def startService(self):
@@ -176,12 +185,14 @@
         """
         Do the export, stopping the reactor when done.
         """
+        self.output.write("\n---- CalVerify version: %s ----\n" % (VERSION,))
+
         try:
             if self.options["missing"]:
                 yield self.doOrphans()
                 
-            if self.options["mismatch"] or self.options["ical"]:
-                yield self.doScan(self.options["ical"], self.options["mismatch"], self.options["fix"])
+            if self.options["mismatch"] or self.options["ical"] or self.options["badcua"]:
+                yield self.doScan(self.options["ical"] or self.options["badcua"], self.options["mismatch"], self.options["fix"])
 
             self.printSummary()
 
@@ -333,10 +344,13 @@
         descriptor = None
         if ical:
             if self.options["uuid"]:
-                rows = yield self.getAllResourceInfoWithUUID(self.options["uuid"])
+                rows = yield self.getAllResourceInfoWithUUID(self.options["uuid"], inbox=True)
                 descriptor = "getAllResourceInfoWithUUID"
+            elif self.options["uid"]:
+                rows = yield self.getAllResourceInfoWithUID(self.options["uid"], inbox=True)
+                descriptor = "getAllResourceInfoWithUID"
             else:
-                rows = yield self.getAllResourceInfo()
+                rows = yield self.getAllResourceInfo(inbox=True)
                 descriptor = "getAllResourceInfo"
         else:
             if self.options["uid"]:
@@ -364,13 +378,19 @@
         self.attended_byuid = collections.defaultdict(list)
         self.matched_attendee_to_organizer = collections.defaultdict(set)
         skipped = 0
-        for owner, resid, uid, md5, organizer, created, modified in rows:
+        inboxes = 0
+        for owner, resid, uid, calname, md5, organizer, created, modified in rows:
             
             # Skip owners not enabled for calendaring
             if not self.testForCalendaringUUID(owner):
                 skipped += 1
                 continue
 
+            # Skip inboxes
+            if calname == "inbox":
+                inboxes += 1
+                continue
+
             # If targeting a specific organizer, skip events belonging to others
             if self.options["uuid"]:
                 if not organizer.startswith("urn:uuid:") or self.options["uuid"] != organizer[9:]:
@@ -389,13 +409,18 @@
         self.results["Number of organizer events to process"] = len(self.organized)
         self.results["Number of attendee events to process"] = len(self.attended)
         self.results["Number of skipped events"] = skipped
+        self.results["Number of inbox events"] = inboxes
         self.addToSummary("Number of organizer events to process", len(self.organized), self.total)
         self.addToSummary("Number of attendee events to process", len(self.attended), self.total)
         self.addToSummary("Number of skipped events", skipped, self.total)
+        if ical:
+            self.addToSummary("Number of inbox events", inboxes, self.total)
+        self.addSummaryBreak()
 
         if ical:
             yield self.calendarDataCheck(rows)
         elif mismatch:
+            self.totalErrors = 0
             yield self.verifyAllAttendeesForOrganizer()
             yield self.verifyAllOrganizersForAttendee()
         
@@ -403,42 +428,56 @@
 
 
     @inlineCallbacks
-    def getAllResourceInfo(self):
+    def getAllResourceInfo(self, inbox=False):
         co = schema.CALENDAR_OBJECT
         cb = schema.CALENDAR_BIND
         ch = schema.CALENDAR_HOME
+        
+        if inbox:
+            cojoin = (cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
+                    cb.BIND_MODE == _BIND_MODE_OWN)
+        else:
+            cojoin = (cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
+                    cb.BIND_MODE == _BIND_MODE_OWN).And(
+                    cb.CALENDAR_RESOURCE_NAME != "inbox")
+
         kwds = {}
         rows = (yield Select(
-            [ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED],
+            [ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, cb.CALENDAR_RESOURCE_NAME, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED],
             From=ch.join(
                 cb, type="inner", on=(ch.RESOURCE_ID == cb.CALENDAR_HOME_RESOURCE_ID)).join(
-                co, type="inner", on=(cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
-                    cb.BIND_MODE == _BIND_MODE_OWN).And(
-                    cb.CALENDAR_RESOURCE_NAME != "inbox")),
-            GroupBy=(ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED,),
+                co, type="inner", on=cojoin),
+            GroupBy=(ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, cb.CALENDAR_RESOURCE_NAME, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED,),
         ).on(self.txn, **kwds))
         returnValue(tuple(rows))
 
 
     @inlineCallbacks
-    def getAllResourceInfoWithUUID(self, uuid):
+    def getAllResourceInfoWithUUID(self, uuid, inbox=False):
         co = schema.CALENDAR_OBJECT
         cb = schema.CALENDAR_BIND
         ch = schema.CALENDAR_HOME
+
+        if inbox:
+            cojoin = (cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
+                    cb.BIND_MODE == _BIND_MODE_OWN)
+        else:
+            cojoin = (cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
+                    cb.BIND_MODE == _BIND_MODE_OWN).And(
+                    cb.CALENDAR_RESOURCE_NAME != "inbox")
+
         kwds = {"uuid": uuid}
         if len(uuid) != 36:
             where = (ch.OWNER_UID.StartsWith(Parameter("uuid")))
         else:
             where = (ch.OWNER_UID == Parameter("uuid"))
         rows = (yield Select(
-            [ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED],
+            [ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, cb.CALENDAR_RESOURCE_NAME, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED],
             From=ch.join(
                 cb, type="inner", on=(ch.RESOURCE_ID == cb.CALENDAR_HOME_RESOURCE_ID)).join(
-                co, type="inner", on=(cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
-                    cb.BIND_MODE == _BIND_MODE_OWN).And(
-                    cb.CALENDAR_RESOURCE_NAME != "inbox")),
+                co, type="inner", on=cojoin),
             Where=where,
-            GroupBy=(ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED,),
+            GroupBy=(ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, cb.CALENDAR_RESOURCE_NAME, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED,),
         ).on(self.txn, **kwds))
         returnValue(tuple(rows))
 
@@ -454,7 +493,7 @@
             "Max"   : pyCalendarTodatetime(PyCalendarDateTime(1900, 1, 1, 0, 0, 0))
         }
         rows = (yield Select(
-            [ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED],
+            [ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, cb.CALENDAR_RESOURCE_NAME, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED],
             From=ch.join(
                 cb, type="inner", on=(ch.RESOURCE_ID == cb.CALENDAR_HOME_RESOURCE_ID)).join(
                 co, type="inner", on=(cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
@@ -463,28 +502,35 @@
                     co.ORGANIZER != "")).join(
                 tr, type="left", on=(co.RESOURCE_ID == tr.CALENDAR_OBJECT_RESOURCE_ID)),
             Where=(tr.START_DATE >= Parameter("Start")).Or(co.RECURRANCE_MAX == Parameter("Max")),
-            GroupBy=(ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED,),
+            GroupBy=(ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, cb.CALENDAR_RESOURCE_NAME, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED,),
         ).on(self.txn, **kwds))
         returnValue(tuple(rows))
 
 
     @inlineCallbacks
-    def getAllResourceInfoWithUID(self, uid):
+    def getAllResourceInfoWithUID(self, uid, inbox=False):
         co = schema.CALENDAR_OBJECT
         cb = schema.CALENDAR_BIND
         ch = schema.CALENDAR_HOME
+
+        if inbox:
+            cojoin = (cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
+                    cb.BIND_MODE == _BIND_MODE_OWN)
+        else:
+            cojoin = (cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
+                    cb.BIND_MODE == _BIND_MODE_OWN).And(
+                    cb.CALENDAR_RESOURCE_NAME != "inbox")
+
         kwds = {
             "UID" : uid,
         }
         rows = (yield Select(
-            [ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED],
+            [ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, cb.CALENDAR_RESOURCE_NAME, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED],
             From=ch.join(
                 cb, type="inner", on=(ch.RESOURCE_ID == cb.CALENDAR_HOME_RESOURCE_ID)).join(
-                co, type="inner", on=(cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
-                    cb.BIND_MODE == _BIND_MODE_OWN).And(
-                    cb.CALENDAR_RESOURCE_NAME != "inbox")),
+                co, type="inner", on=cojoin),
             Where=(co.ICALENDAR_UID == Parameter("UID")),
-            GroupBy=(ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED,),
+            GroupBy=(ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, cb.CALENDAR_RESOURCE_NAME, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED,),
         ).on(self.txn, **kwds))
         returnValue(tuple(rows))
 
@@ -500,8 +546,7 @@
             From=ch.join(
                 cb, type="inner", on=(ch.RESOURCE_ID == cb.CALENDAR_HOME_RESOURCE_ID)).join(
                 co, type="inner", on=(cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
-                    cb.BIND_MODE == _BIND_MODE_OWN).And(
-                    cb.CALENDAR_RESOURCE_NAME != "inbox")),
+                    cb.BIND_MODE == _BIND_MODE_OWN)),
             Where=(co.RESOURCE_ID == Parameter("resid")),
         ).on(self.txn, **kwds))
         returnValue(rows[0])
@@ -541,8 +586,8 @@
         total = len(rows)
         badlen = 0
         rjust = 10
-        for owner, resid, uid, _ignore_md5, _ignore_organizer, _ignore_created, _ignore_modified in rows:
-            result, message = yield self.validCalendarData(resid)
+        for owner, resid, uid, calname, _ignore_md5, _ignore_organizer, _ignore_created, _ignore_modified in rows:
+            result, message = yield self.validCalendarData(resid, calname == "inbox")
             if not result:
                 results_bad.append((owner, uid, resid, message))
                 badlen += 1
@@ -606,22 +651,23 @@
     errorPrefix = "Calendar data had unfixable problems:\n  "
 
     @inlineCallbacks
-    def validCalendarData(self, resid):
+    def validCalendarData(self, resid, isinbox):
         """
         Check the calendar resource for valid iCalendar data.
         """
 
-        caldata = yield self.getCalendar(resid)
+        caldata = yield self.getCalendar(resid, self.fix)
         if caldata is None:
-            returnValue((False, "Failed to parse"))
+            returnValue((False, self.parseError))
 
         component = Component(None, pycalendar=caldata)
         result = True
         message = ""
         try:
-            component.validCalendarData(doFix=False, validateRecurrences=True)
-            component.validCalendarForCalDAV(methodAllowed=False)
-            component.validOrganizerForScheduling(doFix=False)
+            if self.options["ical"]:
+                component.validCalendarData(doFix=False, validateRecurrences=True)
+                component.validCalendarForCalDAV(methodAllowed=isinbox)
+                component.validOrganizerForScheduling(doFix=False)
             self.noPrincipalPathCUAddresses(component, doFix=False)
         except ValueError, e:
             result = False
@@ -631,7 +677,7 @@
             lines = message.splitlines()
             message = lines[0] + (" ++" if len(lines) > 1 else "")
             if self.fix:
-                fixresult, fixmessage = yield self.fixCalendarData(resid)
+                fixresult, fixmessage = yield self.fixCalendarData(resid, isinbox)
                 if fixresult:
                     message = "Fixed: " + message
                 else:
@@ -649,6 +695,12 @@
                 return self.cuaCache[cuaddr]
     
             result = normalizationLookup(cuaddr, principalFunction, config)
+            _ignore_name, guid, _ignore_cuaddrs = result
+            if guid is None:
+                if cuaddr.find("__uids__") != -1:
+                    guid = cuaddr[cuaddr.find("__uids__/")+9:][:36]
+                    result = "", guid, set()
+                    
     
             # Cache the result
             self.cuaCache[cuaddr] = result
@@ -658,20 +710,36 @@
             if subcomponent.name() in ignoredComponents:
                 continue
             organizer = subcomponent.getProperty("ORGANIZER")
-            if organizer and organizer.value().startswith("http"):
-                if doFix:
-                    component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
-                else:
-                    raise InvalidICalendarDataError("iCalendar ORGANIZER starts with 'http(s)'")
+            if organizer:
+                if organizer.value().startswith("http"):
+                    if doFix:
+                        component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
+                    else:
+                        raise InvalidICalendarDataError("iCalendar ORGANIZER starts with 'http(s)'")
+                elif organizer.hasParameter("CALENDARSERVER-OLD-CUA"):
+                    oldcua = organizer.parameterValue("CALENDARSERVER-OLD-CUA")
+                    if not oldcua.startswith("base64-") and not self.options["nobase64"]:
+                        if doFix:
+                            organizer.setParameter("CALENDARSERVER-OLD-CUA", "base64-%s" % (base64.b64encode(oldcua)))
+                        else:
+                            raise InvalidICalendarDataError("iCalendar ORGANIZER CALENDARSERVER-OLD-CUA not base64")
+
             for attendee in subcomponent.properties("ATTENDEE"):
                 if attendee.value().startswith("http"):
                     if doFix:
                         component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
                     else:
                         raise InvalidICalendarDataError("iCalendar ATTENDEE starts with 'http(s)'")
+                elif attendee.hasParameter("CALENDARSERVER-OLD-CUA"):
+                    oldcua = attendee.parameterValue("CALENDARSERVER-OLD-CUA")
+                    if not oldcua.startswith("base64-") and not self.options["nobase64"]:
+                        if doFix:
+                            attendee.setParameter("CALENDARSERVER-OLD-CUA", "base64-%s" % (base64.b64encode(oldcua)))
+                        else:
+                            raise InvalidICalendarDataError("iCalendar ATTENDEE CALENDARSERVER-OLD-CUA not base64")
 
     @inlineCallbacks
-    def fixCalendarData(self, resid):
+    def fixCalendarData(self, resid, isinbox):
         """
         Fix problems in calendar data using store APIs.
         """
@@ -689,9 +757,10 @@
         result = True
         message = ""
         try:
-            component.validCalendarData(doFix=True, validateRecurrences=True)
-            component.validCalendarForCalDAV(methodAllowed=False)
-            component.validOrganizerForScheduling(doFix=True)
+            if self.options["ical"]:
+                component.validCalendarData(doFix=True, validateRecurrences=True)
+                component.validCalendarForCalDAV(methodAllowed=isinbox)
+                component.validOrganizerForScheduling(doFix=True)
             self.noPrincipalPathCUAddresses(component, doFix=True)
         except ValueError:
             result = False
@@ -699,14 +768,83 @@
         
         if result:
             # Write out fix, commit and get a new transaction
-            component = yield calendarObj.setComponent(component)
-            #yield self.txn.commit()
-            #self.txn = self.store.newTransaction()
+            try:
+                # Use _migrating to ignore possible overridden instance errors - we are either correcting or ignoring those
+                self.txn._migrating = True
+                component = yield calendarObj.setComponent(component)
+            except Exception, e:
+                print e, component
+                print traceback.print_exc()
+                result = False
+                message = "Exception fix: "
+            yield self.txn.commit()
+            self.txn = self.store.newTransaction()
 
         returnValue((result, message,))
 
 
     @inlineCallbacks
+    def fixBadOldCua(self, resid, caltxt):
+        """
+        Fix bad CALENDARSERVER-OLD-CUA lines and write fixed data to store. Assumes iCalendar data lines unfolded.
+        """
+
+        # Get store objects
+        homeID, calendarID = yield self.getAllResourceInfoForResourceID(resid)
+        home = yield self.txn.calendarHomeWithResourceID(homeID)
+        calendar = yield home.childWithID(calendarID)
+        calendarObj = yield calendar.objectResourceWithID(resid)
+        
+        # Do raw data fix one line at a time
+        caltxt = self.fixBadOldCuaLines(caltxt)
+        
+        # Re-parse
+        try:
+            component = Component.fromString(caltxt)
+        except InvalidICalendarDataError:
+            returnValue(None)
+
+        # Write out fix, commit and get a new transaction
+        # Use _migrating to ignore possible overridden instance errors - we are either correcting or ignoring those
+        self.txn._migrating = True
+        component = yield calendarObj.setComponent(component)
+        yield self.txn.commit()
+        self.txn = self.store.newTransaction()
+
+        returnValue(caltxt)
+
+
+    def fixBadOldCuaLines(self, caltxt):
+        """
+        Fix bad CALENDARSERVER-OLD-CUA lines. Assumes iCalendar data lines unfolded.
+        """
+
+        # Do raw data fix one line at a time
+        lines = caltxt.splitlines()
+        for ctr, line in enumerate(lines):
+            startpos = line.find(";CALENDARSERVER-OLD-CUA=\"//")
+            if startpos != -1:
+                endpos = line.find("urn:uuid:")
+                if endpos != -1:
+                    endpos += len("urn:uuid:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\"")
+                    badparam = line[startpos+len(";CALENDARSERVER-OLD-CUA=\""):endpos]
+                    endbadparam = badparam.find(";")
+                    if endbadparam != -1:
+                        badparam = badparam[:endbadparam].replace("\\", "")
+                        if badparam.find("8443") != -1:
+                            badparam = "https:" + badparam
+                        else:
+                            badparam = "http:" + badparam
+                        if self.options["nobase64"]:
+                            badparam = "\"" + badparam + "\""
+                        else:
+                            badparam = "base64-%s" % (base64.b64encode(badparam),)
+                        badparam = ";CALENDARSERVER-OLD-CUA=" + badparam
+                        lines[ctr] = line[:startpos] + badparam + line[endpos:]
+        caltxt = "\r\n".join(lines) + "\r\n"
+        return caltxt
+
+    @inlineCallbacks
     def verifyAllAttendeesForOrganizer(self):
         """
         Make sure that for each organizer, each referenced attendee has a consistent view of the organizer's event.
@@ -849,6 +987,7 @@
         self.output.write("Events missing from Attendee's calendars (total=%d):\n" % (len(results_missing),))
         table.printTable(os=self.output)
         self.addToSummary("Events missing from Attendee's calendars", len(results_missing), self.total)
+        self.totalErrors += len(results_missing)
 
         # Print table of results
         table = tables.Table()
@@ -874,6 +1013,7 @@
         self.output.write("Events mismatched between Organizer's and Attendee's calendars (total=%d):\n" % (len(results_mismatch),))
         table.printTable(os=self.output)
         self.addToSummary("Events mismatched between Organizer's and Attendee's calendars", len(results_mismatch), self.total)
+        self.totalErrors += len(results_mismatch)
 
 
     @inlineCallbacks
@@ -982,6 +1122,7 @@
         self.output.write("Attendee events missing in Organizer's calendar (total=%d, unique=%d):\n" % (len(missing), len(unique_set),))
         table.printTable(os=self.output)
         self.addToSummary("Attendee events missing in Organizer's calendar", len(missing), self.total)
+        self.totalErrors += len(missing)
 
         # Print table of results
         table = tables.Table()
@@ -1009,6 +1150,7 @@
         self.output.write("Attendee events mismatched in Organizer's calendar (total=%d):\n" % (len(mismatched),))
         table.printTable(os=self.output)
         self.addToSummary("Attendee events mismatched in Organizer's calendar", len(mismatched), self.total)
+        self.totalErrors += len(mismatched)
 
 
     def addToSummary(self, title, count, total=None):
@@ -1019,6 +1161,10 @@
         self.summary.append((title, count, percent))
 
 
+    def addSummaryBreak(self):
+        self.summary.append(None)
+
+
     def printSummary(self):
         # Print summary of results
         table = tables.Table()
@@ -1031,12 +1177,11 @@
             )
         )
         for item in self.summary:
-            title, result, percent = item
-            table.addRow((
-                title,
-                result,
-                percent,
-            ))
+            table.addRow(item)
+
+        if self.totalErrors is not None:
+            table.addRow(None)
+            table.addRow(("Total Errors", self.totalErrors, safePercent(self.totalErrors, self.total),))
         
         self.output.write("\n")
         self.output.write("Overall Summary:\n")
@@ -1044,7 +1189,7 @@
 
 
     @inlineCallbacks
-    def getCalendar(self, resid):
+    def getCalendar(self, resid, doFix=False):
         co = schema.CALENDAR_OBJECT
         kwds = { "ResourceID" : resid }
         rows = (yield Select(
@@ -1057,7 +1202,24 @@
         try:
             caldata = PyCalendar.parseText(rows[0][0]) if rows else None
         except PyCalendarError:
-            caldata = None
+            caltxt = rows[0][0] if rows else None
+            if caltxt:
+                caltxt = caltxt.replace("\r\n ", "")
+                if caltxt.find("CALENDARSERVER-OLD-CUA=\"//") != -1:
+                    if doFix:
+                        caltxt = (yield self.fixBadOldCua(resid, caltxt))
+                        try:
+                            caldata = PyCalendar.parseText(caltxt) if rows else None
+                        except PyCalendarError:
+                            self.parseError = "No fix bad CALENDARSERVER-OLD-CUA"
+                            returnValue(None)
+                    else:
+                        self.parseError = "Bad CALENDARSERVER-OLD-CUA"
+                        returnValue(None)
+            
+            self.parseError = "Failed to parse"
+            returnValue(None)
+
         returnValue(caldata)
 
 

Modified: CalendarServer/trunk/calendarserver/tools/test/test_calverify.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_calverify.py	2012-04-27 02:35:54 UTC (rev 9207)
+++ CalendarServer/trunk/calendarserver/tools/test/test_calverify.py	2012-04-27 02:38:33 UTC (rev 9208)
@@ -22,7 +22,7 @@
 from calendarserver.tap.util import getRootResource
 from calendarserver.tools.calverify import CalVerifyService
 from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks
+from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.trial import unittest
 from twistedcaldav.config import config
 from txdav.caldav.datastore import util
@@ -223,6 +223,116 @@
 """.replace("\n", "\r\n")
 
 
+# Non-base64 Organizer and Attendee parameter
+BAD7_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD7
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+ORGANIZER;CALENDARSERVER-OLD-CUA="http://demo.com:8008/principals/__uids__/
+ D46F3D71-04B7-43C2-A7B6-6F92F92E61D0":urn:uuid:D46F3D71-04B7-43C2-A7B6-6F9
+ 2F92E61D0
+ATTENDEE;CALENDARSERVER-OLD-CUA="http://demo.com:8008/principals/__uids__/D
+ 46F3D71-04B7-43C2-A7B6-6F92F92E61D0":urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92
+ F92E61D0
+ATTENDEE:mailto:example2 at example.com
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+
+# Base64 Organizer and Attendee parameter
+OK8_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:OK8
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+ORGANIZER;CALENDARSERVER-OLD-CUA="base64-aHR0cDovL2RlbW8uY29tOjgwMDgvcHJpbm
+ NpcGFscy9fX3VpZHNfXy9ENDZGM0Q3MS0wNEI3LTQzQzItQTdCNi02RjkyRjkyRTYxRDA=":
+ urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;CALENDARSERVER-OLD-CUA="base64-aHR0cDovL2RlbW8uY29tOjgwMDgvcHJpbmN
+ pcGFscy9fX3VpZHNfXy9ENDZGM0Q3MS0wNEI3LTQzQzItQTdCNi02RjkyRjkyRTYxRDA=":u
+ rn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:mailto:example2 at example.com
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+BAD9_ICS =                 """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+DTSTART:19621028T020000
+RRULE:FREQ=YEARLY;UNTIL=20061029T090000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19870405T020000
+RRULE:FREQ=YEARLY;UNTIL=20060402T100000Z;BYDAY=1SU;BYMONTH=4
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:BAD9
+DTSTART;TZID=US/Pacific:20111103T150000
+DTEND;TZID=US/Pacific:20111103T160000
+ATTENDEE;CALENDARSERVER-OLD-CUA="//example.com\\:8443/principals/users/cyrus
+ /;CN=\\"Cyrus Daboo\\";CUTYPE=INDIVIDUAL;EMAIL=\\"cyrus at example.com\\";PARTSTAT=ACC
+ EPTED:urn:uuid:7B2636C7-07F6-4475-924B-2854107F7A22";CN=Cyrus Daboo;EMAIL=c
+ yrus at example.com;RSVP=TRUE:urn:uuid:7B2636C7-07F6-4475-924B-2854107F7A22
+ATTENDEE;CN=John Smith;CUTYPE=INDIVIDUAL;EMAIL=smith at example.com;PARTSTAT=AC
+ CEPTED;ROLE=REQ-PARTICIPANT:urn:uuid:E975EB3D-C412-411B-A655-C3BE4949788C
+CREATED:20090730T214912Z
+DTSTAMP:20120421T182823Z
+ORGANIZER;CALENDARSERVER-OLD-CUA="//example.com\\:8443/principals/users/cyru
+ s/;CN=\\"Cyrus Daboo\\";EMAIL=\\"cyrus at example.com\\":urn:uuid:7B2636C7-07F6-4475-9
+ 24B-2854107F7A22";CN=Cyrus Daboo;EMAIL=cyrus at example.com:urn:uuid:7B2636C7-
+ 07F6-4475-924B-2854107F7A22
+RRULE:FREQ=WEEKLY;COUNT=400
+SEQUENCE:18
+SUMMARY:1-on-1
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+
 class CalVerifyTests(CommonCommonTests, unittest.TestCase):
     """
     Tests for deleting events older than a given date
@@ -239,13 +349,16 @@
     requirements = {
         "home1" : {
             "calendar1" : {
-                "ok.ics" : (OK_ICS, metadata,),
+                "ok.ics"   : (OK_ICS, metadata,),
                 "bad1.ics" : (BAD1_ICS, metadata,),
                 "bad2.ics" : (BAD2_ICS, metadata,),
                 "bad3.ics" : (BAD3_ICS, metadata,),
                 "bad4.ics" : (BAD4_ICS, metadata,),
                 "bad5.ics" : (BAD5_ICS, metadata,),
                 "bad6.ics" : (BAD6_ICS, metadata,),
+                "bad7.ics" : (BAD7_ICS, metadata,),
+                "ok8.ics"  : (OK8_ICS, metadata,),
+                "bad9.ics" : (BAD9_ICS, metadata,),
             }
         },
     }
@@ -287,6 +400,26 @@
         return self._sqlCalendarStore
 
 
+    @inlineCallbacks
+    def homeUnderTest(self, txn=None):
+        """
+        Get the calendar home detailed by C{requirements['home1']}.
+        """
+        if txn is None:
+            txn = self.transactionUnderTest()
+        returnValue((yield txn.calendarHomeWithUID("home1")))
+
+
+    @inlineCallbacks
+    def calendarUnderTest(self, txn=None):
+        """
+        Get the calendar detailed by C{requirements['home1']['calendar1']}.
+        """
+        returnValue((yield
+            (yield self.homeUnderTest(txn)).calendarWithName("calendar1"))
+        )
+
+
     def verifyResultsByUID(self, results, expected):
         reported = set([(home, uid) for home, uid, _ignore_resid, _ignore_reason in results])
         self.assertEqual(reported, expected)
@@ -296,18 +429,24 @@
     def test_scanBadData(self):
         """
         CalVerifyService.doScan without fix. Make sure it detects common errors.
+        Make sure sync-token is not changed.
         """
 
+        sync_token_old = (yield (yield self.calendarUnderTest()).syncToken())
+        self.commit()
+
         options = {
-            "ical":None,
+            "ical":True,
+            "nobase64":False,
             "verbose":False,
+            "uid":"",
             "uuid":"",
         }
         output = StringIO()
         calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
         yield calverify.doScan(True, False, False)
 
-        self.assertEqual(calverify.results["Number of events to process"], 7)
+        self.assertEqual(calverify.results["Number of events to process"], 10)
         self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
             ("home1", "BAD1",),
             ("home1", "BAD2",),
@@ -315,18 +454,29 @@
             ("home1", "BAD4",),
             ("home1", "BAD5",),
             ("home1", "BAD6",),
+            ("home1", "BAD7",),
+            ("home1", "BAD9",),
         )))
 
+        sync_token_new = (yield (yield self.calendarUnderTest()).syncToken())
+        self.assertEqual(sync_token_old, sync_token_new)
 
+
     @inlineCallbacks
     def test_fixBadData(self):
         """
-        CalVerifyService.doScan without fix. Make sure it detects and fixes as much as it can.
+        CalVerifyService.doScan with fix. Make sure it detects and fixes as much as it can.
+        Make sure sync-token is changed.
         """
 
+        sync_token_old = (yield (yield self.calendarUnderTest()).syncToken())
+        self.commit()
+
         options = {
-            "ical":None,
+            "ical":True,
+            "nobase64":False,
             "verbose":False,
+            "uid":"",
             "uuid":"",
         }
         output = StringIO()
@@ -337,7 +487,7 @@
         calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
         yield calverify.doScan(True, False, True)
 
-        self.assertEqual(calverify.results["Number of events to process"], 7)
+        self.assertEqual(calverify.results["Number of events to process"], 10)
         self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
             ("home1", "BAD1",),
             ("home1", "BAD2",),
@@ -345,13 +495,303 @@
             ("home1", "BAD4",),
             ("home1", "BAD5",),
             ("home1", "BAD6",),
+            ("home1", "BAD7",),
+            ("home1", "BAD9",),
         )))
 
         # Do scan
         calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
         yield calverify.doScan(True, False, False)
 
-        self.assertEqual(calverify.results["Number of events to process"], 7)
+        self.assertEqual(calverify.results["Number of events to process"], 10)
         self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
             ("home1", "BAD1",),
         )))
+
+        sync_token_new = (yield (yield self.calendarUnderTest()).syncToken())
+        self.assertNotEqual(sync_token_old, sync_token_new)
+
+    @inlineCallbacks
+    def test_scanBadCuaOnly(self):
+        """
+        CalVerifyService.doScan without fix for CALENDARSERVER-OLD-CUA only. Make sure it detects
+        and fixes as much as it can. Make sure sync-token is not changed.
+        """
+
+        sync_token_old = (yield (yield self.calendarUnderTest()).syncToken())
+        self.commit()
+
+        options = {
+            "ical":False,
+            "badcua":True,
+            "nobase64":False,
+            "verbose":False,
+            "uid":"",
+            "uuid":"",
+        }
+        output = StringIO()
+        calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+        yield calverify.doScan(True, False, False)
+
+        self.assertEqual(calverify.results["Number of events to process"], 10)
+        self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
+            ("home1", "BAD4",),
+            ("home1", "BAD5",),
+            ("home1", "BAD6",),
+            ("home1", "BAD7",),
+            ("home1", "BAD9",),
+        )))
+
+        sync_token_new = (yield (yield self.calendarUnderTest()).syncToken())
+        self.assertEqual(sync_token_old, sync_token_new)
+
+    @inlineCallbacks
+    def test_fixBadCuaOnly(self):
+        """
+        CalVerifyService.doScan with fix for CALENDARSERVER-OLD-CUA only. Make sure it detects
+        and fixes as much as it can. Make sure sync-token is changed.
+        """
+
+        sync_token_old = (yield (yield self.calendarUnderTest()).syncToken())
+        self.commit()
+
+        options = {
+            "ical":False,
+            "badcua":True,
+            "nobase64":False,
+            "verbose":False,
+            "uid":"",
+            "uuid":"",
+        }
+        output = StringIO()
+        
+        # Do fix
+        self.patch(config.Scheduling.Options, "PrincipalHostAliases", "demo.com")
+        self.patch(config, "HTTPPort", 8008)
+        calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+        yield calverify.doScan(True, False, True)
+
+        self.assertEqual(calverify.results["Number of events to process"], 10)
+        self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
+            ("home1", "BAD4",),
+            ("home1", "BAD5",),
+            ("home1", "BAD6",),
+            ("home1", "BAD7",),
+            ("home1", "BAD9",),
+        )))
+
+        # Do scan
+        calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+        yield calverify.doScan(True, False, False)
+
+        self.assertEqual(calverify.results["Number of events to process"], 10)
+        self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
+        )))
+
+        sync_token_new = (yield (yield self.calendarUnderTest()).syncToken())
+        self.assertNotEqual(sync_token_old, sync_token_new)
+
+    def test_fixBadCuaLines(self):
+        """
+        CalVerifyService.fixBadOldCuaLines. Make sure it applies correct fix.
+        """
+
+        data = (
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+DTSTART:19621028T020000
+RRULE:FREQ=YEARLY;UNTIL=20061029T090000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19870405T020000
+RRULE:FREQ=YEARLY;UNTIL=20060402T100000Z;BYDAY=1SU;BYMONTH=4
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:32956D5C-579F-46FD-BAE3-4A6C354B8CA3
+DTSTART;TZID=US/Pacific:20111103T150000
+DTEND;TZID=US/Pacific:20111103T160000
+ATTENDEE;CALENDARSERVER-OLD-CUA="//example.com\\:8443/principals/users/cyrus
+ /;CN="Cyrus Daboo";CUTYPE=INDIVIDUAL;EMAIL="cyrus at example.com";PARTSTAT=ACC
+ EPTED:urn:uuid:7B2636C7-07F6-4475-924B-2854107F7A22";CN=Cyrus Daboo;EMAIL=c
+ yrus at example.com;RSVP=TRUE:urn:uuid:7B2636C7-07F6-4475-924B-2854107F7A22
+ATTENDEE;CN=John Smith;CUTYPE=INDIVIDUAL;EMAIL=smith at example.com;PARTSTAT=AC
+ CEPTED;ROLE=REQ-PARTICIPANT:urn:uuid:E975EB3D-C412-411B-A655-C3BE4949788C
+CREATED:20090730T214912Z
+DTSTAMP:20120421T182823Z
+ORGANIZER;CALENDARSERVER-OLD-CUA="//example.com\\:8443/principals/users/cyru
+ s/;CN="Cyrus Daboo";EMAIL="cyrus at example.com":urn:uuid:7B2636C7-07F6-4475-9
+ 24B-2854107F7A22";CN=Cyrus Daboo;EMAIL=cyrus at example.com:urn:uuid:7B2636C7-
+ 07F6-4475-924B-2854107F7A22
+RRULE:FREQ=WEEKLY;COUNT=400
+SEQUENCE:18
+SUMMARY:1-on-1
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+                """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+DTSTART:19621028T020000
+RRULE:FREQ=YEARLY;UNTIL=20061029T090000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19870405T020000
+RRULE:FREQ=YEARLY;UNTIL=20060402T100000Z;BYDAY=1SU;BYMONTH=4
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:32956D5C-579F-46FD-BAE3-4A6C354B8CA3
+DTSTART;TZID=US/Pacific:20111103T150000
+DTEND;TZID=US/Pacific:20111103T160000
+ATTENDEE;CALENDARSERVER-OLD-CUA="https://example.com:8443/principals/users/c
+ yrus/";CN=Cyrus Daboo;EMAIL=cyrus at example.com;RSVP=TRUE:urn:uuid:7B2636C7-0
+ 7F6-4475-924B-2854107F7A22
+ATTENDEE;CN=John Smith;CUTYPE=INDIVIDUAL;EMAIL=smith at example.com;PARTSTAT=AC
+ CEPTED;ROLE=REQ-PARTICIPANT:urn:uuid:E975EB3D-C412-411B-A655-C3BE4949788C
+CREATED:20090730T214912Z
+DTSTAMP:20120421T182823Z
+ORGANIZER;CALENDARSERVER-OLD-CUA="https://example.com:8443/principals/users/
+ cyrus/";CN=Cyrus Daboo;EMAIL=cyrus at example.com:urn:uuid:7B2636C7-07F6-4475-
+ 924B-2854107F7A22
+RRULE:FREQ=WEEKLY;COUNT=400
+SEQUENCE:18
+SUMMARY:1-on-1
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+                """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+DTSTART:19621028T020000
+RRULE:FREQ=YEARLY;UNTIL=20061029T090000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19870405T020000
+RRULE:FREQ=YEARLY;UNTIL=20060402T100000Z;BYDAY=1SU;BYMONTH=4
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:32956D5C-579F-46FD-BAE3-4A6C354B8CA3
+DTSTART;TZID=US/Pacific:20111103T150000
+DTEND;TZID=US/Pacific:20111103T160000
+ATTENDEE;CALENDARSERVER-OLD-CUA=base64-aHR0cHM6Ly9leGFtcGxlLmNvbTo4NDQzL3Bya
+ W5jaXBhbHMvdXNlcnMvY3lydXMv;CN=Cyrus Daboo;EMAIL=cyrus at example.com;RSVP=TRU
+ E:urn:uuid:7B2636C7-07F6-4475-924B-2854107F7A22
+ATTENDEE;CN=John Smith;CUTYPE=INDIVIDUAL;EMAIL=smith at example.com;PARTSTAT=AC
+ CEPTED;ROLE=REQ-PARTICIPANT:urn:uuid:E975EB3D-C412-411B-A655-C3BE4949788C
+CREATED:20090730T214912Z
+DTSTAMP:20120421T182823Z
+ORGANIZER;CALENDARSERVER-OLD-CUA=base64-aHR0cHM6Ly9leGFtcGxlLmNvbTo4NDQzL3By
+ aW5jaXBhbHMvdXNlcnMvY3lydXMv;CN=Cyrus Daboo;EMAIL=cyrus at example.com:urn:uui
+ d:7B2636C7-07F6-4475-924B-2854107F7A22
+RRULE:FREQ=WEEKLY;COUNT=400
+SEQUENCE:18
+SUMMARY:1-on-1
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+            ),
+        )
+        
+        optionsNo64 = {
+            "ical":True,
+            "nobase64":True,
+            "verbose":False,
+            "uid":"",
+            "uuid":"",
+        }
+        calverifyNo64 = CalVerifyService(self._sqlCalendarStore, optionsNo64, StringIO(), reactor, config)
+
+        options64 = {
+            "ical":True,
+            "nobase64":False,
+            "verbose":False,
+            "uid":"",
+            "uuid":"",
+        }
+        calverify64 = CalVerifyService(self._sqlCalendarStore, options64, StringIO(), reactor, config)
+
+        for bad, oknobase64, okbase64 in data:
+            bad = bad.replace("\r\n ", "")
+            oknobase64 = oknobase64.replace("\r\n ", "")
+            okbase64 = okbase64.replace("\r\n ", "")
+            self.assertEqual(calverifyNo64.fixBadOldCuaLines(bad), oknobase64)
+            self.assertEqual(calverify64.fixBadOldCuaLines(bad), okbase64)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120426/208fad97/attachment-0001.html>


More information about the calendarserver-changes mailing list