[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