File indexing completed on 2024-04-28 15:19:13

0001 /*
0002   This file is part of the kcalcore library.
0003 
0004   SPDX-FileCopyrightText: 1998 Preston Brown <pbrown@kde.org>
0005   SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org>
0006 
0007   SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 /**
0010   @file
0011   This file is part of the API for handling calendar data and
0012   defines the VCalFormat base class.
0013 
0014   This class implements the vCalendar format. It provides methods for
0015   loading/saving/converting vCalendar format data into the internal
0016   representation as Calendar and Incidences.
0017 
0018   @brief
0019   vCalendar format implementation.
0020 
0021   @author Preston Brown \<pbrown@kde.org\>
0022   @author Cornelius Schumacher \<schumacher@kde.org\>
0023 */
0024 #include "vcalformat.h"
0025 #include "calendar.h"
0026 #include "calformat_p.h"
0027 #include "exceptions.h"
0028 
0029 #include "kcalendarcore_debug.h"
0030 
0031 extern "C" {
0032 #include <libical/vcc.h>
0033 #include <libical/vobject.h>
0034 }
0035 
0036 #include <QBitArray>
0037 #include <QFile>
0038 #include <QTextDocument> // for .toHtmlEscaped() and Qt::mightBeRichText()
0039 #include <QTimeZone>
0040 
0041 using namespace KCalendarCore;
0042 
0043 /**
0044   Private class that helps to provide binary compatibility between releases.
0045   @internal
0046 */
0047 //@cond PRIVATE
0048 template<typename K>
0049 void removeAllVCal(QVector<QSharedPointer<K>> &c, const QSharedPointer<K> &x)
0050 {
0051     if (c.count() < 1) {
0052         return;
0053     }
0054 
0055     int cnt = c.count(x);
0056     if (cnt != 1) {
0057         qCritical() << "There number of relatedTos for this incidence is " << cnt << " (there must be 1 relatedTo only)";
0058         Q_ASSERT_X(false, "removeAllVCal", "Count is not 1.");
0059         return;
0060     }
0061 
0062     c.remove(c.indexOf(x));
0063 }
0064 
0065 class KCalendarCore::VCalFormatPrivate : public CalFormatPrivate
0066 {
0067 public:
0068     Calendar::Ptr mCalendar;
0069     Event::List mEventsRelate; // Events with relations
0070     Todo::List mTodosRelate; // To-dos with relations
0071     QSet<QByteArray> mManuallyWrittenExtensionFields; // X- fields that are manually dumped
0072 };
0073 //@endcond
0074 
0075 VCalFormat::VCalFormat()
0076     : CalFormat(new KCalendarCore::VCalFormatPrivate)
0077 {
0078 }
0079 
0080 VCalFormat::~VCalFormat()
0081 {
0082 }
0083 
0084 static void mimeErrorHandler(char *e)
0085 {
0086     qCWarning(KCALCORE_LOG) << "Error parsing vCalendar file:" << e;
0087 }
0088 
0089 bool VCalFormat::load(const Calendar::Ptr &calendar, const QString &fileName)
0090 {
0091     Q_D(VCalFormat);
0092     d->mCalendar = calendar;
0093 
0094     clearException();
0095 
0096     // this is not necessarily only 1 vcal.  Could be many vcals, or include
0097     // a vcard...
0098     registerMimeErrorHandler(&mimeErrorHandler);    // Note: vCalendar error handler provided by libical.
0099     VObject *vcal = Parse_MIME_FromFileName(const_cast<char *>(QFile::encodeName(fileName).data()));
0100     registerMimeErrorHandler(nullptr);
0101 
0102     if (!vcal) {
0103         setException(new Exception(Exception::CalVersionUnknown));
0104         return false;
0105     }
0106 
0107     // any other top-level calendar stuff should be added/initialized here
0108 
0109     // put all vobjects into their proper places
0110     auto savedTimeZoneId = d->mCalendar->timeZoneId();
0111     populate(vcal, false, fileName);
0112     d->mCalendar->setTimeZoneId(savedTimeZoneId);
0113 
0114     // clean up from vcal API stuff
0115     cleanVObjects(vcal);
0116     cleanStrTbl();
0117 
0118     return true;
0119 }
0120 
0121 bool VCalFormat::save(const Calendar::Ptr &calendar, const QString &fileName)
0122 {
0123     Q_UNUSED(calendar);
0124     Q_UNUSED(fileName);
0125     qCWarning(KCALCORE_LOG) << "Saving VCAL is not supported";
0126     return false;
0127 }
0128 
0129 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 97)
0130 bool VCalFormat::fromString(const Calendar::Ptr &calendar, const QString &string, bool deleted, const QString &notebook)
0131 {
0132     return fromRawString(calendar, string.toUtf8(), deleted, notebook);
0133 }
0134 #endif
0135 
0136 bool VCalFormat::fromRawString(const Calendar::Ptr &calendar, const QByteArray &string, bool deleted, const QString &notebook)
0137 {
0138     Q_D(VCalFormat);
0139     d->mCalendar = calendar;
0140 
0141     if (!string.size()) {
0142         return false;
0143     }
0144 
0145     VObject *vcal = Parse_MIME(string.data(), string.size());
0146     if (!vcal) {
0147         return false;
0148     }
0149 
0150     VObjectIterator i;
0151     initPropIterator(&i, vcal);
0152 
0153     // put all vobjects into their proper places
0154     auto savedTimeZoneId = d->mCalendar->timeZoneId();
0155     populate(vcal, deleted, notebook);
0156     d->mCalendar->setTimeZoneId(savedTimeZoneId);
0157 
0158     // clean up from vcal API stuff
0159     cleanVObjects(vcal);
0160     cleanStrTbl();
0161 
0162     return true;
0163 }
0164 
0165 QString VCalFormat::toString(const Calendar::Ptr &calendar, const QString &notebook, bool deleted)
0166 {
0167     Q_UNUSED(calendar);
0168     Q_UNUSED(notebook);
0169     Q_UNUSED(deleted);
0170 
0171     qCWarning(KCALCORE_LOG) << "Exporting into VCAL is not supported";
0172     return {};
0173 }
0174 
0175 Todo::Ptr VCalFormat::VTodoToEvent(VObject *vtodo)
0176 {
0177     Q_D(VCalFormat);
0178     VObject *vo = nullptr;
0179     VObjectIterator voi;
0180     char *s = nullptr;
0181 
0182     Todo::Ptr anEvent(new Todo);
0183 
0184     // creation date
0185     if ((vo = isAPropertyOf(vtodo, VCDCreatedProp)) != nullptr) {
0186         anEvent->setCreated(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
0187         deleteStr(s);
0188     }
0189 
0190     // unique id
0191     vo = isAPropertyOf(vtodo, VCUniqueStringProp);
0192     // while the UID property is preferred, it is not required.  We'll use the
0193     // default Event UID if none is given.
0194     if (vo) {
0195         anEvent->setUid(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
0196         deleteStr(s);
0197     }
0198 
0199     // last modification date
0200     if ((vo = isAPropertyOf(vtodo, VCLastModifiedProp)) != nullptr) {
0201         anEvent->setLastModified(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
0202         deleteStr(s);
0203     } else {
0204         anEvent->setLastModified(QDateTime::currentDateTimeUtc());
0205     }
0206 
0207     // organizer
0208     // if our extension property for the event's ORGANIZER exists, add it.
0209     if ((vo = isAPropertyOf(vtodo, ICOrganizerProp)) != nullptr) {
0210         anEvent->setOrganizer(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
0211         deleteStr(s);
0212     } else {
0213         if (d->mCalendar->owner().name() != QLatin1String("Unknown Name")) {
0214             anEvent->setOrganizer(d->mCalendar->owner());
0215         }
0216     }
0217 
0218     // attendees.
0219     initPropIterator(&voi, vtodo);
0220     while (moreIteration(&voi)) {
0221         vo = nextVObject(&voi);
0222         if (strcmp(vObjectName(vo), VCAttendeeProp) == 0) {
0223             Attendee a;
0224             VObject *vp;
0225             s = fakeCString(vObjectUStringZValue(vo));
0226             QString tmpStr = QString::fromUtf8(s);
0227             deleteStr(s);
0228             tmpStr = tmpStr.simplified();
0229             int emailPos1;
0230             if ((emailPos1 = tmpStr.indexOf(QLatin1Char('<'))) > 0) {
0231                 // both email address and name
0232                 int emailPos2 = tmpStr.lastIndexOf(QLatin1Char('>'));
0233                 a = Attendee(tmpStr.left(emailPos1 - 1), tmpStr.mid(emailPos1 + 1, emailPos2 - (emailPos1 + 1)));
0234             } else if (tmpStr.indexOf(QLatin1Char('@')) > 0) {
0235                 // just an email address
0236                 a = Attendee(QString(), tmpStr);
0237             } else {
0238                 // just a name
0239                 // WTF??? Replacing the spaces of a name and using this as email?
0240                 QString email = tmpStr.replace(QLatin1Char(' '), QLatin1Char('.'));
0241                 a = Attendee(tmpStr, email);
0242             }
0243 
0244             // is there an RSVP property?
0245             if ((vp = isAPropertyOf(vo, VCRSVPProp)) != nullptr) {
0246                 a.setRSVP(vObjectStringZValue(vp));
0247             }
0248             // is there a status property?
0249             if ((vp = isAPropertyOf(vo, VCStatusProp)) != nullptr) {
0250                 a.setStatus(readStatus(vObjectStringZValue(vp)));
0251             }
0252             // add the attendee
0253             anEvent->addAttendee(a);
0254         }
0255     }
0256 
0257     // description for todo
0258     if ((vo = isAPropertyOf(vtodo, VCDescriptionProp)) != nullptr) {
0259         s = fakeCString(vObjectUStringZValue(vo));
0260         anEvent->setDescription(QString::fromUtf8(s), Qt::mightBeRichText(QString::fromUtf8(s)));
0261         deleteStr(s);
0262     }
0263 
0264     // summary
0265     if ((vo = isAPropertyOf(vtodo, VCSummaryProp))) {
0266         s = fakeCString(vObjectUStringZValue(vo));
0267         anEvent->setSummary(QString::fromUtf8(s), Qt::mightBeRichText(QString::fromUtf8(s)));
0268         deleteStr(s);
0269     }
0270 
0271     // location
0272     if ((vo = isAPropertyOf(vtodo, VCLocationProp)) != nullptr) {
0273         s = fakeCString(vObjectUStringZValue(vo));
0274         anEvent->setLocation(QString::fromUtf8(s), Qt::mightBeRichText(QString::fromUtf8(s)));
0275         deleteStr(s);
0276     }
0277 
0278     // completed
0279     // was: status
0280     if ((vo = isAPropertyOf(vtodo, VCStatusProp)) != nullptr) {
0281         s = fakeCString(vObjectUStringZValue(vo));
0282         if (s && strcmp(s, "COMPLETED") == 0) {
0283             anEvent->setCompleted(true);
0284         } else {
0285             anEvent->setCompleted(false);
0286         }
0287         deleteStr(s);
0288     } else {
0289         anEvent->setCompleted(false);
0290     }
0291 
0292     // completion date
0293     if ((vo = isAPropertyOf(vtodo, VCCompletedProp)) != nullptr) {
0294         anEvent->setCompleted(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
0295         deleteStr(s);
0296     }
0297 
0298     // priority
0299     if ((vo = isAPropertyOf(vtodo, VCPriorityProp))) {
0300         s = fakeCString(vObjectUStringZValue(vo));
0301         if (s) {
0302             anEvent->setPriority(atoi(s));
0303             deleteStr(s);
0304         }
0305     }
0306 
0307     anEvent->setAllDay(false);
0308 
0309     // due date
0310     if ((vo = isAPropertyOf(vtodo, VCDueProp)) != nullptr) {
0311         anEvent->setDtDue(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
0312         deleteStr(s);
0313         if (anEvent->dtDue().time().hour() == 0 && anEvent->dtDue().time().minute() == 0 && anEvent->dtDue().time().second() == 0) {
0314             anEvent->setAllDay(true);
0315         }
0316     } else {
0317         anEvent->setDtDue(QDateTime());
0318     }
0319 
0320     // start time
0321     if ((vo = isAPropertyOf(vtodo, VCDTstartProp)) != nullptr) {
0322         anEvent->setDtStart(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
0323         deleteStr(s);
0324         if (anEvent->dtStart().time().hour() == 0 && anEvent->dtStart().time().minute() == 0 && anEvent->dtStart().time().second() == 0) {
0325             anEvent->setAllDay(true);
0326         }
0327     } else {
0328         anEvent->setDtStart(QDateTime());
0329     }
0330 
0331     // recurrence stuff
0332     if ((vo = isAPropertyOf(vtodo, VCRRuleProp)) != nullptr) {
0333         uint recurrenceType = Recurrence::rNone;
0334         int recurrenceTypeAbbrLen = 0;
0335 
0336         s = fakeCString(vObjectUStringZValue(vo));
0337         QString tmpStr = QString::fromUtf8(s);
0338         deleteStr(s);
0339         tmpStr = tmpStr.simplified();
0340         const int tmpStrLen = tmpStr.length();
0341         if (tmpStrLen > 0) {
0342             tmpStr = tmpStr.toUpper();
0343             QStringView prefix = QStringView(tmpStr).left(2);
0344 
0345             // first, read the type of the recurrence
0346             recurrenceTypeAbbrLen = 1;
0347             if (tmpStr.at(0) == QLatin1Char('D')) {
0348                 recurrenceType = Recurrence::rDaily;
0349             } else if (tmpStr.at(0) == QLatin1Char('W')) {
0350                 recurrenceType = Recurrence::rWeekly;
0351             } else if (tmpStrLen > 1) {
0352                 recurrenceTypeAbbrLen = 2;
0353                 if (prefix == QLatin1String("MP")) {
0354                     recurrenceType = Recurrence::rMonthlyPos;
0355                 } else if (prefix == QLatin1String("MD")) {
0356                     recurrenceType = Recurrence::rMonthlyDay;
0357                 } else if (prefix == QLatin1String("YM")) {
0358                     recurrenceType = Recurrence::rYearlyMonth;
0359                 } else if (prefix == QLatin1String("YD")) {
0360                     recurrenceType = Recurrence::rYearlyDay;
0361                 }
0362             }
0363         }
0364 
0365         if (recurrenceType != Recurrence::rNone) {
0366             // Immediately after the type is the frequency
0367             int index = tmpStr.indexOf(QLatin1Char(' '));
0368             int last = tmpStr.lastIndexOf(QLatin1Char(' ')) + 1; // find last entry
0369 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0370             const int rFreq = QStringView(tmpStr).mid(recurrenceTypeAbbrLen, (index - 1)).toInt();
0371 #else
0372             const int rFreq = tmpStr.midRef(recurrenceTypeAbbrLen, (index - 1)).toInt();
0373 #endif
0374             ++index; // advance to beginning of stuff after freq
0375 
0376             // Read the type-specific settings
0377             switch (recurrenceType) {
0378             case Recurrence::rDaily:
0379                 anEvent->recurrence()->setDaily(rFreq);
0380                 break;
0381 
0382             case Recurrence::rWeekly: {
0383                 QBitArray qba(7);
0384                 QString dayStr;
0385                 if (index == last) {
0386                     // e.g. W1 #0
0387                     qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1);
0388                 } else {
0389                     // e.g. W1 SU #0
0390                     while (index < last) {
0391                         dayStr = tmpStr.mid(index, 3);
0392                         int dayNum = numFromDay(dayStr);
0393                         if (dayNum >= 0) {
0394                             qba.setBit(dayNum);
0395                         }
0396                         index += 3; // advance to next day, or possibly "#"
0397                     }
0398                 }
0399                 anEvent->recurrence()->setWeekly(rFreq, qba);
0400                 break;
0401             }
0402 
0403             case Recurrence::rMonthlyPos: {
0404                 anEvent->recurrence()->setMonthly(rFreq);
0405 
0406                 QBitArray qba(7);
0407                 short tmpPos;
0408                 if (index == last) {
0409                     // e.g. MP1 #0
0410                     tmpPos = anEvent->dtStart().date().day() / 7 + 1;
0411                     if (tmpPos == 5) {
0412                         tmpPos = -1;
0413                     }
0414                     qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1);
0415                     anEvent->recurrence()->addMonthlyPos(tmpPos, qba);
0416                 } else {
0417                     // e.g. MP1 1+ SU #0
0418                     while (index < last) {
0419                         tmpPos = tmpStr.mid(index, 1).toShort();
0420                         index += 1;
0421                         if (tmpStr.mid(index, 1) == QLatin1String("-")) {
0422                             // convert tmpPos to negative
0423                             tmpPos = 0 - tmpPos;
0424                         }
0425                         index += 2; // advance to day(s)
0426                         while (numFromDay(tmpStr.mid(index, 3)) >= 0) {
0427                             int dayNum = numFromDay(tmpStr.mid(index, 3));
0428                             qba.setBit(dayNum);
0429                             index += 3; // advance to next day, or possibly pos or "#"
0430                         }
0431                         anEvent->recurrence()->addMonthlyPos(tmpPos, qba);
0432                         qba.detach();
0433                         qba.fill(false); // clear out
0434                     } // while != "#"
0435                 }
0436                 break;
0437             }
0438 
0439             case Recurrence::rMonthlyDay:
0440                 anEvent->recurrence()->setMonthly(rFreq);
0441                 if (index == last) {
0442                     // e.g. MD1 #0
0443                     short tmpDay = anEvent->dtStart().date().day();
0444                     anEvent->recurrence()->addMonthlyDate(tmpDay);
0445                 } else {
0446                     // e.g. MD1 3 #0
0447                     while (index < last) {
0448                         int index2 = tmpStr.indexOf(QLatin1Char(' '), index);
0449                         if ((tmpStr.mid((index2 - 1), 1) == QLatin1String("-")) || (tmpStr.mid((index2 - 1), 1) == QLatin1String("+"))) {
0450                             index2 = index2 - 1;
0451                         }
0452                         short tmpDay = tmpStr.mid(index, (index2 - index)).toShort();
0453                         index = index2;
0454                         if (tmpStr.mid(index, 1) == QLatin1String("-")) {
0455                             tmpDay = 0 - tmpDay;
0456                         }
0457                         index += 2; // advance the index;
0458                         anEvent->recurrence()->addMonthlyDate(tmpDay);
0459                     } // while != #
0460                 }
0461                 break;
0462 
0463             case Recurrence::rYearlyMonth:
0464                 anEvent->recurrence()->setYearly(rFreq);
0465 
0466                 if (index == last) {
0467                     // e.g. YM1 #0
0468                     short tmpMonth = anEvent->dtStart().date().month();
0469                     anEvent->recurrence()->addYearlyMonth(tmpMonth);
0470                 } else {
0471                     // e.g. YM1 3 #0
0472                     while (index < last) {
0473                         int index2 = tmpStr.indexOf(QLatin1Char(' '), index);
0474                         short tmpMonth = tmpStr.mid(index, (index2 - index)).toShort();
0475                         index = index2 + 1;
0476                         anEvent->recurrence()->addYearlyMonth(tmpMonth);
0477                     } // while != #
0478                 }
0479                 break;
0480 
0481             case Recurrence::rYearlyDay:
0482                 anEvent->recurrence()->setYearly(rFreq);
0483 
0484                 if (index == last) {
0485                     // e.g. YD1 #0
0486                     short tmpDay = anEvent->dtStart().date().dayOfYear();
0487                     anEvent->recurrence()->addYearlyDay(tmpDay);
0488                 } else {
0489                     // e.g. YD1 123 #0
0490                     while (index < last) {
0491                         int index2 = tmpStr.indexOf(QLatin1Char(' '), index);
0492                         short tmpDay = tmpStr.mid(index, (index2 - index)).toShort();
0493                         index = index2 + 1;
0494                         anEvent->recurrence()->addYearlyDay(tmpDay);
0495                     } // while != #
0496                 }
0497                 break;
0498 
0499             default:
0500                 break;
0501             }
0502 
0503             // find the last field, which is either the duration or the end date
0504             index = last;
0505             if (tmpStr.mid(index, 1) == QLatin1String("#")) {
0506                 // Nr of occurrences
0507                 ++index;
0508 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0509                 const int rDuration = QStringView(tmpStr).mid(index, tmpStr.length() - index).toInt();
0510 #else
0511                 const int rDuration = tmpStr.midRef(index, tmpStr.length() - index).toInt();
0512 #endif
0513                 if (rDuration > 0) {
0514                     anEvent->recurrence()->setDuration(rDuration);
0515                 }
0516             } else if (tmpStr.indexOf(QLatin1Char('T'), index) != -1) {
0517                 QDateTime rEndDate = ISOToQDateTime(tmpStr.mid(index, tmpStr.length() - index));
0518                 anEvent->recurrence()->setEndDateTime(rEndDate);
0519             }
0520         } else {
0521             qCDebug(KCALCORE_LOG) << "we don't understand this type of recurrence!";
0522         } // if known recurrence type
0523     } // repeats
0524 
0525     // recurrence exceptions
0526     if ((vo = isAPropertyOf(vtodo, VCExpDateProp)) != nullptr) {
0527         s = fakeCString(vObjectUStringZValue(vo));
0528         const QStringList exDates = QString::fromUtf8(s).split(QLatin1Char(','));
0529         for (const auto &date : exDates) {
0530             const QDateTime exDate = ISOToQDateTime(date);
0531             if (exDate.time().hour() == 0 && exDate.time().minute() == 0 && exDate.time().second() == 0) {
0532                 anEvent->recurrence()->addExDate(ISOToQDate(date));
0533             } else {
0534                 anEvent->recurrence()->addExDateTime(exDate);
0535             }
0536         }
0537         deleteStr(s);
0538     }
0539 
0540     // alarm stuff
0541     if ((vo = isAPropertyOf(vtodo, VCDAlarmProp))) {
0542         Alarm::Ptr alarm;
0543         VObject *a = isAPropertyOf(vo, VCRunTimeProp);
0544         VObject *b = isAPropertyOf(vo, VCDisplayStringProp);
0545 
0546         if (a || b) {
0547             alarm = anEvent->newAlarm();
0548             if (a) {
0549                 alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a)))));
0550                 deleteStr(s);
0551             }
0552             alarm->setEnabled(true);
0553             if (b) {
0554                 s = fakeCString(vObjectUStringZValue(b));
0555                 alarm->setDisplayAlarm(QString::fromUtf8(s));
0556                 deleteStr(s);
0557             } else {
0558                 alarm->setDisplayAlarm(QString());
0559             }
0560         }
0561     }
0562 
0563     if ((vo = isAPropertyOf(vtodo, VCAAlarmProp))) {
0564         Alarm::Ptr alarm;
0565         VObject *a;
0566         VObject *b;
0567         a = isAPropertyOf(vo, VCRunTimeProp);
0568         b = isAPropertyOf(vo, VCAudioContentProp);
0569 
0570         if (a || b) {
0571             alarm = anEvent->newAlarm();
0572             if (a) {
0573                 alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a)))));
0574                 deleteStr(s);
0575             }
0576             alarm->setEnabled(true);
0577             if (b) {
0578                 s = fakeCString(vObjectUStringZValue(b));
0579                 alarm->setAudioAlarm(QFile::decodeName(s));
0580                 deleteStr(s);
0581             } else {
0582                 alarm->setAudioAlarm(QString());
0583             }
0584         }
0585     }
0586 
0587     if ((vo = isAPropertyOf(vtodo, VCPAlarmProp))) {
0588         Alarm::Ptr alarm;
0589         VObject *a = isAPropertyOf(vo, VCRunTimeProp);
0590         VObject *b = isAPropertyOf(vo, VCProcedureNameProp);
0591 
0592         if (a || b) {
0593             alarm = anEvent->newAlarm();
0594             if (a) {
0595                 alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a)))));
0596                 deleteStr(s);
0597             }
0598             alarm->setEnabled(true);
0599 
0600             if (b) {
0601                 s = fakeCString(vObjectUStringZValue(b));
0602                 alarm->setProcedureAlarm(QFile::decodeName(s));
0603                 deleteStr(s);
0604             } else {
0605                 alarm->setProcedureAlarm(QString());
0606             }
0607         }
0608     }
0609 
0610     // related todo
0611     if ((vo = isAPropertyOf(vtodo, VCRelatedToProp)) != nullptr) {
0612         anEvent->setRelatedTo(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
0613         deleteStr(s);
0614         d->mTodosRelate.append(anEvent);
0615     }
0616 
0617     // secrecy
0618     Incidence::Secrecy secrecy = Incidence::SecrecyPublic;
0619     if ((vo = isAPropertyOf(vtodo, VCClassProp)) != nullptr) {
0620         s = fakeCString(vObjectUStringZValue(vo));
0621         if (s && strcmp(s, "PRIVATE") == 0) {
0622             secrecy = Incidence::SecrecyPrivate;
0623         } else if (s && strcmp(s, "CONFIDENTIAL") == 0) {
0624             secrecy = Incidence::SecrecyConfidential;
0625         }
0626         deleteStr(s);
0627     }
0628     anEvent->setSecrecy(secrecy);
0629 
0630     // categories
0631     if ((vo = isAPropertyOf(vtodo, VCCategoriesProp)) != nullptr) {
0632         s = fakeCString(vObjectUStringZValue(vo));
0633         QString categories = QString::fromUtf8(s);
0634         deleteStr(s);
0635         QStringList tmpStrList = categories.split(QLatin1Char(';'));
0636         anEvent->setCategories(tmpStrList);
0637     }
0638 
0639     return anEvent;
0640 }
0641 
0642 Event::Ptr VCalFormat::VEventToEvent(VObject *vevent)
0643 {
0644     Q_D(VCalFormat);
0645     VObject *vo = nullptr;
0646     VObjectIterator voi;
0647     char *s = nullptr;
0648 
0649     Event::Ptr anEvent(new Event);
0650 
0651     // creation date
0652     if ((vo = isAPropertyOf(vevent, VCDCreatedProp)) != nullptr) {
0653         anEvent->setCreated(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
0654         deleteStr(s);
0655     }
0656 
0657     // unique id
0658     vo = isAPropertyOf(vevent, VCUniqueStringProp);
0659     // while the UID property is preferred, it is not required.  We'll use the
0660     // default Event UID if none is given.
0661     if (vo) {
0662         anEvent->setUid(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
0663         deleteStr(s);
0664     }
0665 
0666     // revision
0667     // again NSCAL doesn't give us much to work with, so we improvise...
0668     anEvent->setRevision(0);
0669     if ((vo = isAPropertyOf(vevent, VCSequenceProp)) != nullptr) {
0670         s = fakeCString(vObjectUStringZValue(vo));
0671         if (s) {
0672             anEvent->setRevision(atoi(s));
0673             deleteStr(s);
0674         }
0675     }
0676 
0677     // last modification date
0678     if ((vo = isAPropertyOf(vevent, VCLastModifiedProp)) != nullptr) {
0679         anEvent->setLastModified(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
0680         deleteStr(s);
0681     } else {
0682         anEvent->setLastModified(QDateTime::currentDateTimeUtc());
0683     }
0684 
0685     // organizer
0686     // if our extension property for the event's ORGANIZER exists, add it.
0687     if ((vo = isAPropertyOf(vevent, ICOrganizerProp)) != nullptr) {
0688         // FIXME:  Also use the full name, not just the email address
0689         anEvent->setOrganizer(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
0690         deleteStr(s);
0691     } else {
0692         if (d->mCalendar->owner().name() != QLatin1String("Unknown Name")) {
0693             anEvent->setOrganizer(d->mCalendar->owner());
0694         }
0695     }
0696 
0697     // deal with attendees.
0698     initPropIterator(&voi, vevent);
0699     while (moreIteration(&voi)) {
0700         vo = nextVObject(&voi);
0701         if (strcmp(vObjectName(vo), VCAttendeeProp) == 0) {
0702             Attendee a;
0703             VObject *vp = nullptr;
0704             s = fakeCString(vObjectUStringZValue(vo));
0705             QString tmpStr = QString::fromUtf8(s);
0706             deleteStr(s);
0707             tmpStr = tmpStr.simplified();
0708             int emailPos1;
0709             if ((emailPos1 = tmpStr.indexOf(QLatin1Char('<'))) > 0) {
0710                 // both email address and name
0711                 int emailPos2 = tmpStr.lastIndexOf(QLatin1Char('>'));
0712                 a = Attendee(tmpStr.left(emailPos1 - 1), tmpStr.mid(emailPos1 + 1, emailPos2 - (emailPos1 + 1)));
0713             } else if (tmpStr.indexOf(QLatin1Char('@')) > 0) {
0714                 // just an email address
0715                 a = Attendee(QString(), tmpStr);
0716             } else {
0717                 // just a name
0718                 QString email = tmpStr.replace(QLatin1Char(' '), QLatin1Char('.'));
0719                 a = Attendee(tmpStr, email);
0720             }
0721 
0722             // is there an RSVP property?
0723             if ((vp = isAPropertyOf(vo, VCRSVPProp)) != nullptr) {
0724                 a.setRSVP(vObjectStringZValue(vp));
0725             }
0726             // is there a status property?
0727             if ((vp = isAPropertyOf(vo, VCStatusProp)) != nullptr) {
0728                 a.setStatus(readStatus(vObjectStringZValue(vp)));
0729             }
0730             // add the attendee
0731             anEvent->addAttendee(a);
0732         }
0733     }
0734 
0735     // This isn't strictly true.  An event that doesn't have a start time
0736     // or an end time isn't all-day, it has an anchor in time but it doesn't
0737     // "take up" any time.
0738     /*if ((isAPropertyOf(vevent, VCDTstartProp) == 0) ||
0739         (isAPropertyOf(vevent, VCDTendProp) == 0)) {
0740       anEvent->setAllDay(true);
0741       } else {
0742       }*/
0743 
0744     anEvent->setAllDay(false);
0745 
0746     // start time
0747     if ((vo = isAPropertyOf(vevent, VCDTstartProp)) != nullptr) {
0748         anEvent->setDtStart(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
0749         deleteStr(s);
0750 
0751         if (anEvent->dtStart().time().hour() == 0 && anEvent->dtStart().time().minute() == 0 && anEvent->dtStart().time().second() == 0) {
0752             anEvent->setAllDay(true);
0753         }
0754     }
0755 
0756     // stop time
0757     if ((vo = isAPropertyOf(vevent, VCDTendProp)) != nullptr) {
0758         anEvent->setDtEnd(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
0759         deleteStr(s);
0760 
0761         if (anEvent->dtEnd().time().hour() == 0 && anEvent->dtEnd().time().minute() == 0 && anEvent->dtEnd().time().second() == 0) {
0762             anEvent->setAllDay(true);
0763         }
0764     }
0765 
0766     // at this point, there should be at least a start or end time.
0767     // fix up for events that take up no time but have a time associated
0768     if (!isAPropertyOf(vevent, VCDTstartProp)) {
0769         anEvent->setDtStart(anEvent->dtEnd());
0770     }
0771     if (!isAPropertyOf(vevent, VCDTendProp)) {
0772         anEvent->setDtEnd(anEvent->dtStart());
0773     }
0774 
0775     ///////////////////////////////////////////////////////////////////////////
0776 
0777     // recurrence stuff
0778     if ((vo = isAPropertyOf(vevent, VCRRuleProp)) != nullptr) {
0779         uint recurrenceType = Recurrence::rNone;
0780         int recurrenceTypeAbbrLen = 0;
0781 
0782         QString tmpStr = (QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
0783         deleteStr(s);
0784         tmpStr = tmpStr.simplified();
0785         const int tmpStrLen = tmpStr.length();
0786         if (tmpStrLen > 0) {
0787             tmpStr = tmpStr.toUpper();
0788             const QStringView prefix(tmpStr.left(2));
0789 
0790             // first, read the type of the recurrence
0791             recurrenceTypeAbbrLen = 1;
0792             if (tmpStr.at(0) == QLatin1Char('D')) {
0793                 recurrenceType = Recurrence::rDaily;
0794             } else if (tmpStr.at(0) == QLatin1Char('W')) {
0795                 recurrenceType = Recurrence::rWeekly;
0796             } else if (tmpStrLen > 1) {
0797                 recurrenceTypeAbbrLen = 2;
0798                 if (prefix == QLatin1String("MP")) {
0799                     recurrenceType = Recurrence::rMonthlyPos;
0800                 } else if (prefix == QLatin1String("MD")) {
0801                     recurrenceType = Recurrence::rMonthlyDay;
0802                 } else if (prefix == QLatin1String("YM")) {
0803                     recurrenceType = Recurrence::rYearlyMonth;
0804                 } else if (prefix == QLatin1String("YD")) {
0805                     recurrenceType = Recurrence::rYearlyDay;
0806                 }
0807             }
0808         }
0809 
0810         if (recurrenceType != Recurrence::rNone) {
0811             // Immediately after the type is the frequency
0812             int index = tmpStr.indexOf(QLatin1Char(' '));
0813             int last = tmpStr.lastIndexOf(QLatin1Char(' ')) + 1; // find last entry
0814 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0815             const int rFreq = QStringView(tmpStr).mid(recurrenceTypeAbbrLen, (index - 1)).toInt();
0816 #else
0817             const int rFreq = tmpStr.midRef(recurrenceTypeAbbrLen, (index - 1)).toInt();
0818 #endif
0819             ++index; // advance to beginning of stuff after freq
0820 
0821             // Read the type-specific settings
0822             switch (recurrenceType) {
0823             case Recurrence::rDaily:
0824                 anEvent->recurrence()->setDaily(rFreq);
0825                 break;
0826 
0827             case Recurrence::rWeekly: {
0828                 QBitArray qba(7);
0829                 QString dayStr;
0830                 if (index == last) {
0831                     // e.g. W1 #0
0832                     qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1);
0833                 } else {
0834                     // e.g. W1 SU #0
0835                     while (index < last) {
0836                         dayStr = tmpStr.mid(index, 3);
0837                         int dayNum = numFromDay(dayStr);
0838                         if (dayNum >= 0) {
0839                             qba.setBit(dayNum);
0840                         }
0841                         index += 3; // advance to next day, or possibly "#"
0842                     }
0843                 }
0844                 anEvent->recurrence()->setWeekly(rFreq, qba);
0845                 break;
0846             }
0847 
0848             case Recurrence::rMonthlyPos: {
0849                 anEvent->recurrence()->setMonthly(rFreq);
0850 
0851                 QBitArray qba(7);
0852                 short tmpPos;
0853                 if (index == last) {
0854                     // e.g. MP1 #0
0855                     tmpPos = anEvent->dtStart().date().day() / 7 + 1;
0856                     if (tmpPos == 5) {
0857                         tmpPos = -1;
0858                     }
0859                     qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1);
0860                     anEvent->recurrence()->addMonthlyPos(tmpPos, qba);
0861                 } else {
0862                     // e.g. MP1 1+ SU #0
0863                     while (index < last) {
0864                         tmpPos = tmpStr.mid(index, 1).toShort();
0865                         index += 1;
0866                         if (tmpStr.mid(index, 1) == QLatin1String("-")) {
0867                             // convert tmpPos to negative
0868                             tmpPos = 0 - tmpPos;
0869                         }
0870                         index += 2; // advance to day(s)
0871                         while (numFromDay(tmpStr.mid(index, 3)) >= 0) {
0872                             int dayNum = numFromDay(tmpStr.mid(index, 3));
0873                             qba.setBit(dayNum);
0874                             index += 3; // advance to next day, or possibly pos or "#"
0875                         }
0876                         anEvent->recurrence()->addMonthlyPos(tmpPos, qba);
0877                         qba.detach();
0878                         qba.fill(false); // clear out
0879                     } // while != "#"
0880                 }
0881                 break;
0882             }
0883 
0884             case Recurrence::rMonthlyDay:
0885                 anEvent->recurrence()->setMonthly(rFreq);
0886                 if (index == last) {
0887                     // e.g. MD1 #0
0888                     short tmpDay = anEvent->dtStart().date().day();
0889                     anEvent->recurrence()->addMonthlyDate(tmpDay);
0890                 } else {
0891                     // e.g. MD1 3 #0
0892                     while (index < last) {
0893                         int index2 = tmpStr.indexOf(QLatin1Char(' '), index);
0894                         if ((tmpStr.mid((index2 - 1), 1) == QLatin1String("-")) || (tmpStr.mid((index2 - 1), 1) == QLatin1String("+"))) {
0895                             index2 = index2 - 1;
0896                         }
0897                         short tmpDay = tmpStr.mid(index, (index2 - index)).toShort();
0898                         index = index2;
0899                         if (tmpStr.mid(index, 1) == QLatin1String("-")) {
0900                             tmpDay = 0 - tmpDay;
0901                         }
0902                         index += 2; // advance the index;
0903                         anEvent->recurrence()->addMonthlyDate(tmpDay);
0904                     } // while != #
0905                 }
0906                 break;
0907 
0908             case Recurrence::rYearlyMonth:
0909                 anEvent->recurrence()->setYearly(rFreq);
0910 
0911                 if (index == last) {
0912                     // e.g. YM1 #0
0913                     short tmpMonth = anEvent->dtStart().date().month();
0914                     anEvent->recurrence()->addYearlyMonth(tmpMonth);
0915                 } else {
0916                     // e.g. YM1 3 #0
0917                     while (index < last) {
0918                         int index2 = tmpStr.indexOf(QLatin1Char(' '), index);
0919                         short tmpMonth = tmpStr.mid(index, (index2 - index)).toShort();
0920                         index = index2 + 1;
0921                         anEvent->recurrence()->addYearlyMonth(tmpMonth);
0922                     } // while != #
0923                 }
0924                 break;
0925 
0926             case Recurrence::rYearlyDay:
0927                 anEvent->recurrence()->setYearly(rFreq);
0928 
0929                 if (index == last) {
0930                     // e.g. YD1 #0
0931                     const int tmpDay = anEvent->dtStart().date().dayOfYear();
0932                     anEvent->recurrence()->addYearlyDay(tmpDay);
0933                 } else {
0934                     // e.g. YD1 123 #0
0935                     while (index < last) {
0936                         int index2 = tmpStr.indexOf(QLatin1Char(' '), index);
0937                         short tmpDay = tmpStr.mid(index, (index2 - index)).toShort();
0938                         index = index2 + 1;
0939                         anEvent->recurrence()->addYearlyDay(tmpDay);
0940                     } // while != #
0941                 }
0942                 break;
0943 
0944             default:
0945                 break;
0946             }
0947 
0948             // find the last field, which is either the duration or the end date
0949             index = last;
0950             if (tmpStr.mid(index, 1) == QLatin1String("#")) {
0951                 // Nr of occurrences
0952                 ++index;
0953 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0954                 const int rDuration = QStringView(tmpStr).mid(index, tmpStr.length() - index).toInt();
0955 #else
0956                 const int rDuration = tmpStr.midRef(index, tmpStr.length() - index).toInt();
0957 #endif
0958                 if (rDuration > 0) {
0959                     anEvent->recurrence()->setDuration(rDuration);
0960                 }
0961             } else if (tmpStr.indexOf(QLatin1Char('T'), index) != -1) {
0962                 QDateTime rEndDate = ISOToQDateTime(tmpStr.mid(index, tmpStr.length() - index));
0963                 anEvent->recurrence()->setEndDateTime(rEndDate);
0964             }
0965             // anEvent->recurrence()->dump();
0966 
0967         } else {
0968             qCDebug(KCALCORE_LOG) << "we don't understand this type of recurrence!";
0969         } // if known recurrence type
0970     } // repeats
0971 
0972     // recurrence exceptions
0973     if ((vo = isAPropertyOf(vevent, VCExpDateProp)) != nullptr) {
0974         s = fakeCString(vObjectUStringZValue(vo));
0975         const QStringList exDates = QString::fromUtf8(s).split(QLatin1Char(','));
0976         for (const auto &date : exDates) {
0977             const QDateTime exDate = ISOToQDateTime(date);
0978             if (exDate.time().hour() == 0 && exDate.time().minute() == 0 && exDate.time().second() == 0) {
0979                 anEvent->recurrence()->addExDate(ISOToQDate(date));
0980             } else {
0981                 anEvent->recurrence()->addExDateTime(exDate);
0982             }
0983         }
0984         deleteStr(s);
0985     }
0986 
0987     // summary
0988     if ((vo = isAPropertyOf(vevent, VCSummaryProp))) {
0989         s = fakeCString(vObjectUStringZValue(vo));
0990         anEvent->setSummary(QString::fromUtf8(s), Qt::mightBeRichText(QString::fromUtf8(s)));
0991         deleteStr(s);
0992     }
0993 
0994     // description
0995     if ((vo = isAPropertyOf(vevent, VCDescriptionProp)) != nullptr) {
0996         s = fakeCString(vObjectUStringZValue(vo));
0997         bool isRich = Qt::mightBeRichText(QString::fromUtf8(s));
0998         if (!anEvent->description().isEmpty()) {
0999             anEvent->setDescription(anEvent->description() + QLatin1Char('\n') + QString::fromUtf8(s), isRich);
1000         } else {
1001             anEvent->setDescription(QString::fromUtf8(s), isRich);
1002         }
1003         deleteStr(s);
1004     }
1005 
1006     // location
1007     if ((vo = isAPropertyOf(vevent, VCLocationProp)) != nullptr) {
1008         s = fakeCString(vObjectUStringZValue(vo));
1009         anEvent->setLocation(QString::fromUtf8(s), Qt::mightBeRichText(QString::fromUtf8(s)));
1010         deleteStr(s);
1011     }
1012 
1013     // some stupid vCal exporters ignore the standard and use Description
1014     // instead of Summary for the default field.  Correct for this.
1015     if (anEvent->summary().isEmpty() && !(anEvent->description().isEmpty())) {
1016         QString tmpStr = anEvent->description().simplified();
1017         anEvent->setDescription(QString());
1018         anEvent->setSummary(tmpStr);
1019     }
1020 
1021 #if 0
1022     // status
1023     if ((vo = isAPropertyOf(vevent, VCStatusProp)) != 0) {
1024         QString tmpStr(s = fakeCString(vObjectUStringZValue(vo)));
1025         deleteStr(s);
1026 // TODO: Define Event status
1027 //    anEvent->setStatus( tmpStr );
1028     } else {
1029 //    anEvent->setStatus( "NEEDS ACTION" );
1030     }
1031 #endif
1032 
1033     // secrecy
1034     Incidence::Secrecy secrecy = Incidence::SecrecyPublic;
1035     if ((vo = isAPropertyOf(vevent, VCClassProp)) != nullptr) {
1036         s = fakeCString(vObjectUStringZValue(vo));
1037         if (s && strcmp(s, "PRIVATE") == 0) {
1038             secrecy = Incidence::SecrecyPrivate;
1039         } else if (s && strcmp(s, "CONFIDENTIAL") == 0) {
1040             secrecy = Incidence::SecrecyConfidential;
1041         }
1042         deleteStr(s);
1043     }
1044     anEvent->setSecrecy(secrecy);
1045 
1046     // categories
1047     if ((vo = isAPropertyOf(vevent, VCCategoriesProp)) != nullptr) {
1048         s = fakeCString(vObjectUStringZValue(vo));
1049         QString categories = QString::fromUtf8(s);
1050         deleteStr(s);
1051         QStringList tmpStrList = categories.split(QLatin1Char(','));
1052         anEvent->setCategories(tmpStrList);
1053     }
1054 
1055     // attachments
1056     initPropIterator(&voi, vevent);
1057     while (moreIteration(&voi)) {
1058         vo = nextVObject(&voi);
1059         if (strcmp(vObjectName(vo), VCAttachProp) == 0) {
1060             s = fakeCString(vObjectUStringZValue(vo));
1061             anEvent->addAttachment(Attachment(QString::fromUtf8(s)));
1062             deleteStr(s);
1063         }
1064     }
1065 
1066     // resources
1067     if ((vo = isAPropertyOf(vevent, VCResourcesProp)) != nullptr) {
1068         QString resources = (QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
1069         deleteStr(s);
1070         QStringList tmpStrList = resources.split(QLatin1Char(';'));
1071         anEvent->setResources(tmpStrList);
1072     }
1073 
1074     // alarm stuff
1075     if ((vo = isAPropertyOf(vevent, VCDAlarmProp))) {
1076         Alarm::Ptr alarm;
1077         VObject *a = isAPropertyOf(vo, VCRunTimeProp);
1078         VObject *b = isAPropertyOf(vo, VCDisplayStringProp);
1079 
1080         if (a || b) {
1081             alarm = anEvent->newAlarm();
1082             if (a) {
1083                 alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a)))));
1084                 deleteStr(s);
1085             }
1086             alarm->setEnabled(true);
1087 
1088             if (b) {
1089                 s = fakeCString(vObjectUStringZValue(b));
1090                 alarm->setDisplayAlarm(QString::fromUtf8(s));
1091                 deleteStr(s);
1092             } else {
1093                 alarm->setDisplayAlarm(QString());
1094             }
1095         }
1096     }
1097 
1098     if ((vo = isAPropertyOf(vevent, VCAAlarmProp))) {
1099         Alarm::Ptr alarm;
1100         VObject *a;
1101         VObject *b;
1102         a = isAPropertyOf(vo, VCRunTimeProp);
1103         b = isAPropertyOf(vo, VCAudioContentProp);
1104 
1105         if (a || b) {
1106             alarm = anEvent->newAlarm();
1107             if (a) {
1108                 alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a)))));
1109                 deleteStr(s);
1110             }
1111             alarm->setEnabled(true);
1112 
1113             if (b) {
1114                 s = fakeCString(vObjectUStringZValue(b));
1115                 alarm->setAudioAlarm(QFile::decodeName(s));
1116                 deleteStr(s);
1117             } else {
1118                 alarm->setAudioAlarm(QString());
1119             }
1120         }
1121     }
1122 
1123     if ((vo = isAPropertyOf(vevent, VCPAlarmProp))) {
1124         Alarm::Ptr alarm;
1125         VObject *a;
1126         VObject *b;
1127         a = isAPropertyOf(vo, VCRunTimeProp);
1128         b = isAPropertyOf(vo, VCProcedureNameProp);
1129 
1130         if (a || b) {
1131             alarm = anEvent->newAlarm();
1132             if (a) {
1133                 alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a)))));
1134                 deleteStr(s);
1135             }
1136             alarm->setEnabled(true);
1137 
1138             if (b) {
1139                 s = fakeCString(vObjectUStringZValue(b));
1140                 alarm->setProcedureAlarm(QFile::decodeName(s));
1141                 deleteStr(s);
1142             } else {
1143                 alarm->setProcedureAlarm(QString());
1144             }
1145         }
1146     }
1147 
1148     // priority
1149     if ((vo = isAPropertyOf(vevent, VCPriorityProp))) {
1150         s = fakeCString(vObjectUStringZValue(vo));
1151         if (s) {
1152             anEvent->setPriority(atoi(s));
1153             deleteStr(s);
1154         }
1155     }
1156 
1157     // transparency
1158     if ((vo = isAPropertyOf(vevent, VCTranspProp)) != nullptr) {
1159         s = fakeCString(vObjectUStringZValue(vo));
1160         if (s) {
1161             int i = atoi(s);
1162             anEvent->setTransparency(i == 1 ? Event::Transparent : Event::Opaque);
1163             deleteStr(s);
1164         }
1165     }
1166 
1167     // related event
1168     if ((vo = isAPropertyOf(vevent, VCRelatedToProp)) != nullptr) {
1169         anEvent->setRelatedTo(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
1170         deleteStr(s);
1171         d->mEventsRelate.append(anEvent);
1172     }
1173 
1174     /* Rest of the custom properties */
1175     readCustomProperties(vevent, anEvent);
1176 
1177     return anEvent;
1178 }
1179 
1180 QString VCalFormat::parseTZ(const QByteArray &timezone) const
1181 {
1182     // qCDebug(KCALCORE_LOG) << timezone;
1183     QString pZone = QString::fromUtf8(timezone.mid(timezone.indexOf("TZID:VCAL") + 9));
1184     return pZone.mid(0, pZone.indexOf(QLatin1Char('\n')));
1185 }
1186 
1187 QString VCalFormat::parseDst(QByteArray &timezone) const
1188 {
1189     if (!timezone.contains("BEGIN:DAYLIGHT")) {
1190         return QString();
1191     }
1192 
1193     timezone = timezone.mid(timezone.indexOf("BEGIN:DAYLIGHT"));
1194     timezone = timezone.mid(timezone.indexOf("TZNAME:") + 7);
1195     QString sStart = QString::fromUtf8(timezone.mid(0, (timezone.indexOf("COMMENT:"))));
1196     sStart.chop(2);
1197     timezone = timezone.mid(timezone.indexOf("TZOFFSETTO:") + 11);
1198     QString sOffset = QString::fromUtf8(timezone.mid(0, (timezone.indexOf("DTSTART:"))));
1199     sOffset.chop(2);
1200     sOffset.insert(3, QLatin1Char(':'));
1201     timezone = timezone.mid(timezone.indexOf("TZNAME:") + 7);
1202     QString sEnd = QString::fromUtf8(timezone.mid(0, (timezone.indexOf("COMMENT:"))));
1203     sEnd.chop(2);
1204 
1205     return QStringLiteral("TRUE;") + sOffset + QLatin1Char(';') + sStart + QLatin1Char(';') + sEnd + QLatin1String(";;");
1206 }
1207 
1208 QString VCalFormat::qDateToISO(const QDate &qd)
1209 {
1210     if (!qd.isValid()) {
1211         return QString();
1212     }
1213 
1214     return QString::asprintf("%.2d%.2d%.2d", qd.year(), qd.month(), qd.day());
1215 }
1216 
1217 QString VCalFormat::qDateTimeToISO(const QDateTime &dt, bool zulu)
1218 {
1219     Q_D(VCalFormat);
1220     if (!dt.isValid()) {
1221         return QString();
1222     }
1223 
1224     QDateTime tmpDT;
1225     if (zulu) {
1226         tmpDT = dt.toUTC();
1227     } else {
1228         tmpDT = dt.toTimeZone(d->mCalendar->timeZone());
1229     }
1230     QString tmpStr = QString::asprintf("%.2d%.2d%.2dT%.2d%.2d%.2d",
1231                                        tmpDT.date().year(),
1232                                        tmpDT.date().month(),
1233                                        tmpDT.date().day(),
1234                                        tmpDT.time().hour(),
1235                                        tmpDT.time().minute(),
1236                                        tmpDT.time().second());
1237     if (zulu || dt.timeZone() == QTimeZone::utc()) {
1238         tmpStr += QLatin1Char('Z');
1239     }
1240     return tmpStr;
1241 }
1242 
1243 QDateTime VCalFormat::ISOToQDateTime(const QString &dtStr)
1244 {
1245     Q_D(VCalFormat);
1246 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1247     auto noAllocString = QStringView{dtStr};
1248 #else
1249     auto noAllocString = QStringRef(&dtStr);
1250 #endif
1251 
1252     int year = noAllocString.left(4).toInt();
1253     int month = noAllocString.mid(4, 2).toInt();
1254     int day = noAllocString.mid(6, 2).toInt();
1255     int hour = noAllocString.mid(9, 2).toInt();
1256     int minute = noAllocString.mid(11, 2).toInt();
1257     int second = noAllocString.mid(13, 2).toInt();
1258 
1259     QDate tmpDate;
1260     tmpDate.setDate(year, month, day);
1261     QTime tmpTime;
1262     tmpTime.setHMS(hour, minute, second);
1263 
1264     if (tmpDate.isValid() && tmpTime.isValid()) {
1265         // correct for GMT if string is in Zulu format
1266         if (dtStr.at(dtStr.length() - 1) == QLatin1Char('Z')) {
1267             return QDateTime(tmpDate, tmpTime, Qt::UTC);
1268         } else {
1269             return QDateTime(tmpDate, tmpTime, d->mCalendar->timeZone());
1270         }
1271     }
1272 
1273     return {};
1274 }
1275 
1276 QDate VCalFormat::ISOToQDate(const QString &dateStr)
1277 {
1278 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1279     auto noAllocString = QStringView{dateStr};
1280 #else
1281     auto noAllocString = QStringRef(&dateStr);
1282 #endif
1283 
1284     const int year = noAllocString.left(4).toInt();
1285     const int month = noAllocString.mid(4, 2).toInt();
1286     const int day = noAllocString.mid(6, 2).toInt();
1287 
1288     return QDate(year, month, day);
1289 }
1290 
1291 bool VCalFormat::parseTZOffsetISO8601(const QString &s, int &result)
1292 {
1293     // ISO8601 format(s):
1294     // +- hh : mm
1295     // +- hh mm
1296     // +- hh
1297 
1298     // We also accept broken one without +
1299     int mod = 1;
1300     int v = 0;
1301     const QString str = s.trimmed();
1302     int ofs = 0;
1303     result = 0;
1304 
1305     // Check for end
1306     if (str.size() <= ofs) {
1307         return false;
1308     }
1309     if (str[ofs] == QLatin1Char('-')) {
1310         mod = -1;
1311         ofs++;
1312     } else if (str[ofs] == QLatin1Char('+')) {
1313         ofs++;
1314     }
1315     if (str.size() <= ofs) {
1316         return false;
1317     }
1318 
1319     // Make sure next two values are numbers
1320     bool ok;
1321 
1322     if (str.size() < (ofs + 2)) {
1323         return false;
1324     }
1325 
1326 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1327     v = QStringView(str).mid(ofs, 2).toInt(&ok) * 60;
1328 #else
1329     v = str.midRef(ofs, 2).toInt(&ok) * 60;
1330 #endif
1331     if (!ok) {
1332         return false;
1333     }
1334     ofs += 2;
1335 
1336     if (str.size() > ofs) {
1337         if (str[ofs] == QLatin1Char(':')) {
1338             ofs++;
1339         }
1340         if (str.size() > ofs) {
1341             if (str.size() < (ofs + 2)) {
1342                 return false;
1343             }
1344 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1345             v += QStringView(str).mid(ofs, 2).toInt(&ok);
1346 #else
1347             v += str.midRef(ofs, 2).toInt(&ok);
1348 #endif
1349             if (!ok) {
1350                 return false;
1351             }
1352         }
1353     }
1354     result = v * mod * 60;
1355     return true;
1356 }
1357 
1358 // take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc.
1359 // and break it down from it's tree-like format into the dictionary format
1360 // that is used internally in the VCalFormat.
1361 void VCalFormat::populate(VObject *vcal, bool deleted, const QString &notebook)
1362 {
1363     Q_D(VCalFormat);
1364     Q_UNUSED(notebook);
1365     // this function will populate the caldict dictionary and other event
1366     // lists. It turns vevents into Events and then inserts them.
1367 
1368     VObjectIterator i;
1369     VObject *curVO;
1370     Event::Ptr anEvent;
1371     bool hasTimeZone = false; // The calendar came with a TZ and not UTC
1372     QTimeZone previousZone; // If we add a new TZ we should leave the spec as it was before
1373 
1374     if ((curVO = isAPropertyOf(vcal, ICMethodProp)) != nullptr) {
1375         char *methodType = fakeCString(vObjectUStringZValue(curVO));
1376         // qCDebug(KCALCORE_LOG) << "This calendar is an iTIP transaction of type '" << methodType << "'";
1377         deleteStr(methodType);
1378     }
1379 
1380     // warn the user that we might have trouble reading non-known calendar.
1381     if ((curVO = isAPropertyOf(vcal, VCProdIdProp)) != nullptr) {
1382         char *s = fakeCString(vObjectUStringZValue(curVO));
1383         if (!s || strcmp(productId().toUtf8().constData(), s) != 0) {
1384             qCDebug(KCALCORE_LOG) << "This vCalendar file was not created by KOrganizer or"
1385                                   << "any other product we support. Loading anyway...";
1386         }
1387         setLoadedProductId(QString::fromUtf8(s));
1388         deleteStr(s);
1389     }
1390 
1391     // warn the user we might have trouble reading this unknown version.
1392     if ((curVO = isAPropertyOf(vcal, VCVersionProp)) != nullptr) {
1393         char *s = fakeCString(vObjectUStringZValue(curVO));
1394         if (!s || strcmp(_VCAL_VERSION, s) != 0) {
1395             qCDebug(KCALCORE_LOG) << "This vCalendar file has version" << s << "We only support" << _VCAL_VERSION;
1396         }
1397         deleteStr(s);
1398     }
1399 
1400     // set the time zone (this is a property of the view, so just discard!)
1401     if ((curVO = isAPropertyOf(vcal, VCTimeZoneProp)) != nullptr) {
1402         char *s = fakeCString(vObjectUStringZValue(curVO));
1403         QString ts = QString::fromUtf8(s);
1404         QString name = QLatin1String("VCAL") + ts;
1405         deleteStr(s);
1406 
1407         // TODO: While using the timezone-offset + vcal as timezone is is
1408         // most likely unique, we should REALLY actually create something
1409         // like vcal-tzoffset-daylightoffsets, or better yet,
1410         // vcal-hash<the former>
1411 
1412         QStringList tzList;
1413         QString tz;
1414         int utcOffset;
1415         if (parseTZOffsetISO8601(ts, utcOffset)) {
1416             // qCDebug(KCALCORE_LOG) << "got standard offset" << ts << utcOffset;
1417             // standard from tz
1418             // starting date for now 01011900
1419             QDateTime dt = QDateTime(QDateTime(QDate(1900, 1, 1), QTime(0, 0, 0)));
1420             tz = QStringLiteral("STD;%1;false;%2").arg(QString::number(utcOffset), dt.toString());
1421             tzList.append(tz);
1422 
1423             // go through all the daylight tags
1424             initPropIterator(&i, vcal);
1425             while (moreIteration(&i)) {
1426                 curVO = nextVObject(&i);
1427                 if (strcmp(vObjectName(curVO), VCDayLightProp) == 0) {
1428                     char *s = fakeCString(vObjectUStringZValue(curVO));
1429                     QString dst = QLatin1String(s);
1430                     QStringList argl = dst.split(QLatin1Char(','));
1431                     deleteStr(s);
1432 
1433                     // Too short -> not interesting
1434                     if (argl.size() < 4) {
1435                         continue;
1436                     }
1437 
1438                     // We don't care about the non-DST periods
1439                     if (argl[0] != QLatin1String("TRUE")) {
1440                         continue;
1441                     }
1442 
1443                     int utcOffsetDst;
1444                     if (parseTZOffsetISO8601(argl[1], utcOffsetDst)) {
1445                         // qCDebug(KCALCORE_LOG) << "got DST offset" << argl[1] << utcOffsetDst;
1446                         // standard
1447                         QString strEndDate = argl[3];
1448                         QDateTime endDate = ISOToQDateTime(strEndDate);
1449                         // daylight
1450                         QString strStartDate = argl[2];
1451                         QDateTime startDate = ISOToQDateTime(strStartDate);
1452 
1453                         QString strRealEndDate = strEndDate;
1454                         QString strRealStartDate = strStartDate;
1455                         QDateTime realEndDate = endDate;
1456                         QDateTime realStartDate = startDate;
1457                         // if we get dates for some reason in wrong order, earlier is used for dst
1458                         if (endDate < startDate) {
1459                             strRealEndDate = strStartDate;
1460                             strRealStartDate = strEndDate;
1461                             realEndDate = startDate;
1462                             realStartDate = endDate;
1463                         }
1464                         tz = QStringLiteral("%1;%2;false;%3").arg(strRealEndDate, QString::number(utcOffset), realEndDate.toString());
1465                         tzList.append(tz);
1466 
1467                         tz = QStringLiteral("%1;%2;true;%3").arg(strRealStartDate, QString::number(utcOffsetDst), realStartDate.toString());
1468                         tzList.append(tz);
1469                     } else {
1470                         qCDebug(KCALCORE_LOG) << "unable to parse dst" << argl[1];
1471                     }
1472                 }
1473             }
1474             if (!QTimeZone::isTimeZoneIdAvailable(name.toLatin1())) {
1475                 qCDebug(KCALCORE_LOG) << "zone is not valid, parsing error" << tzList;
1476             } else {
1477                 previousZone = d->mCalendar->timeZone();
1478                 d->mCalendar->setTimeZoneId(name.toUtf8());
1479                 hasTimeZone = true;
1480             }
1481         } else {
1482             qCDebug(KCALCORE_LOG) << "unable to parse tzoffset" << ts;
1483         }
1484     }
1485 
1486     // Store all events with a relatedTo property in a list for post-processing
1487     d->mEventsRelate.clear();
1488     d->mTodosRelate.clear();
1489 
1490     initPropIterator(&i, vcal);
1491 
1492     // go through all the vobjects in the vcal
1493     while (moreIteration(&i)) {
1494         curVO = nextVObject(&i);
1495 
1496         /************************************************************************/
1497 
1498         // now, check to see that the object is an event or todo.
1499         if (strcmp(vObjectName(curVO), VCEventProp) == 0) {
1500             if (!isAPropertyOf(curVO, VCDTstartProp) && !isAPropertyOf(curVO, VCDTendProp)) {
1501                 qCDebug(KCALCORE_LOG) << "found a VEvent with no DTSTART and no DTEND! Skipping...";
1502                 goto SKIP;
1503             }
1504 
1505             anEvent = VEventToEvent(curVO);
1506             if (anEvent) {
1507                 if (hasTimeZone && !anEvent->allDay() && anEvent->dtStart().timeZone() == QTimeZone::utc()) {
1508                     // This sounds stupid but is how others are doing it, so here
1509                     // we go. If there is a TZ in the VCALENDAR even if the dtStart
1510                     // and dtend are in UTC, clients interpret it using also the TZ defined
1511                     // in the Calendar. I know it sounds braindead but oh well
1512                     int utcOffSet = anEvent->dtStart().offsetFromUtc();
1513                     QDateTime dtStart(anEvent->dtStart().addSecs(utcOffSet));
1514                     dtStart.setTimeZone(d->mCalendar->timeZone());
1515                     QDateTime dtEnd(anEvent->dtEnd().addSecs(utcOffSet));
1516                     dtEnd.setTimeZone(d->mCalendar->timeZone());
1517                     anEvent->setDtStart(dtStart);
1518                     anEvent->setDtEnd(dtEnd);
1519                 }
1520                 Event::Ptr old =
1521                     !anEvent->hasRecurrenceId() ? d->mCalendar->event(anEvent->uid()) : d->mCalendar->event(anEvent->uid(), anEvent->recurrenceId());
1522 
1523                 if (old) {
1524                     if (deleted) {
1525                         d->mCalendar->deleteEvent(old); // move old to deleted
1526                         removeAllVCal(d->mEventsRelate, old);
1527                     } else if (anEvent->revision() > old->revision()) {
1528                         d->mCalendar->deleteEvent(old); // move old to deleted
1529                         removeAllVCal(d->mEventsRelate, old);
1530                         d->mCalendar->addEvent(anEvent); // and replace it with this one
1531                     }
1532                 } else if (deleted) {
1533                     old = !anEvent->hasRecurrenceId() ? d->mCalendar->deletedEvent(anEvent->uid())
1534                                                       : d->mCalendar->deletedEvent(anEvent->uid(), anEvent->recurrenceId());
1535                     if (!old) {
1536                         d->mCalendar->addEvent(anEvent); // add this one
1537                         d->mCalendar->deleteEvent(anEvent); // and move it to deleted
1538                     }
1539                 } else {
1540                     d->mCalendar->addEvent(anEvent); // just add this one
1541                 }
1542             }
1543         } else if (strcmp(vObjectName(curVO), VCTodoProp) == 0) {
1544             Todo::Ptr aTodo = VTodoToEvent(curVO);
1545             if (aTodo) {
1546                 if (hasTimeZone && !aTodo->allDay() && aTodo->dtStart().timeZone() == QTimeZone::utc()) {
1547                     // This sounds stupid but is how others are doing it, so here
1548                     // we go. If there is a TZ in the VCALENDAR even if the dtStart
1549                     // and dtend are in UTC, clients interpret it using also the TZ defined
1550                     // in the Calendar. I know it sounds braindead but oh well
1551                     int utcOffSet = aTodo->dtStart().offsetFromUtc();
1552                     QDateTime dtStart(aTodo->dtStart().addSecs(utcOffSet));
1553                     dtStart.setTimeZone(d->mCalendar->timeZone());
1554                     aTodo->setDtStart(dtStart);
1555                     if (aTodo->hasDueDate()) {
1556                         QDateTime dtDue(aTodo->dtDue().addSecs(utcOffSet));
1557                         dtDue.setTimeZone(d->mCalendar->timeZone());
1558                         aTodo->setDtDue(dtDue);
1559                     }
1560                 }
1561                 Todo::Ptr old = !aTodo->hasRecurrenceId() ? d->mCalendar->todo(aTodo->uid()) : d->mCalendar->todo(aTodo->uid(), aTodo->recurrenceId());
1562                 if (old) {
1563                     if (deleted) {
1564                         d->mCalendar->deleteTodo(old); // move old to deleted
1565                         removeAllVCal(d->mTodosRelate, old);
1566                     } else if (aTodo->revision() > old->revision()) {
1567                         d->mCalendar->deleteTodo(old); // move old to deleted
1568                         removeAllVCal(d->mTodosRelate, old);
1569                         d->mCalendar->addTodo(aTodo); // and replace it with this one
1570                     }
1571                 } else if (deleted) {
1572                     old = d->mCalendar->deletedTodo(aTodo->uid(), aTodo->recurrenceId());
1573                     if (!old) {
1574                         d->mCalendar->addTodo(aTodo); // add this one
1575                         d->mCalendar->deleteTodo(aTodo); // and move it to deleted
1576                     }
1577                 } else {
1578                     d->mCalendar->addTodo(aTodo); // just add this one
1579                 }
1580             }
1581         } else if ((strcmp(vObjectName(curVO), VCVersionProp) == 0) || (strcmp(vObjectName(curVO), VCProdIdProp) == 0)
1582                    || (strcmp(vObjectName(curVO), VCTimeZoneProp) == 0)) {
1583             // do nothing, we know these properties and we want to skip them.
1584             // we have either already processed them or are ignoring them.
1585             ;
1586         } else if (strcmp(vObjectName(curVO), VCDayLightProp) == 0) {
1587             // do nothing daylights are already processed
1588             ;
1589         } else {
1590             qCDebug(KCALCORE_LOG) << "Ignoring unknown vObject \"" << vObjectName(curVO) << "\"";
1591         }
1592     SKIP:;
1593     } // while
1594 
1595     // Post-Process list of events with relations, put Event objects in relation
1596     for (const auto &eventPtr : std::as_const(d->mEventsRelate)) {
1597         eventPtr->setRelatedTo(eventPtr->relatedTo());
1598     }
1599 
1600     for (const auto &todoPtr : std::as_const(d->mTodosRelate)) {
1601         todoPtr->setRelatedTo(todoPtr->relatedTo());
1602     }
1603 
1604     // Now lets put the TZ back as it was if we have changed it.
1605     if (hasTimeZone) {
1606         d->mCalendar->setTimeZone(previousZone);
1607     }
1608 }
1609 
1610 int VCalFormat::numFromDay(const QString &day)
1611 {
1612     if (day == QLatin1String("MO ")) {
1613         return 0;
1614     }
1615     if (day == QLatin1String("TU ")) {
1616         return 1;
1617     }
1618     if (day == QLatin1String("WE ")) {
1619         return 2;
1620     }
1621     if (day == QLatin1String("TH ")) {
1622         return 3;
1623     }
1624     if (day == QLatin1String("FR ")) {
1625         return 4;
1626     }
1627     if (day == QLatin1String("SA ")) {
1628         return 5;
1629     }
1630     if (day == QLatin1String("SU ")) {
1631         return 6;
1632     }
1633 
1634     return -1; // something bad happened. :)
1635 }
1636 
1637 Attendee::PartStat VCalFormat::readStatus(const char *s) const
1638 {
1639     QString statStr = QString::fromUtf8(s);
1640     statStr = statStr.toUpper();
1641     Attendee::PartStat status;
1642 
1643     if (statStr == QLatin1String("X-ACTION")) {
1644         status = Attendee::NeedsAction;
1645     } else if (statStr == QLatin1String("NEEDS ACTION")) {
1646         status = Attendee::NeedsAction;
1647     } else if (statStr == QLatin1String("ACCEPTED")) {
1648         status = Attendee::Accepted;
1649     } else if (statStr == QLatin1String("SENT")) {
1650         status = Attendee::NeedsAction;
1651     } else if (statStr == QLatin1String("TENTATIVE")) {
1652         status = Attendee::Tentative;
1653     } else if (statStr == QLatin1String("CONFIRMED")) {
1654         status = Attendee::Accepted;
1655     } else if (statStr == QLatin1String("DECLINED")) {
1656         status = Attendee::Declined;
1657     } else if (statStr == QLatin1String("COMPLETED")) {
1658         status = Attendee::Completed;
1659     } else if (statStr == QLatin1String("DELEGATED")) {
1660         status = Attendee::Delegated;
1661     } else {
1662         qCDebug(KCALCORE_LOG) << "error setting attendee mStatus, unknown mStatus!";
1663         status = Attendee::NeedsAction;
1664     }
1665 
1666     return status;
1667 }
1668 
1669 QByteArray VCalFormat::writeStatus(Attendee::PartStat status) const
1670 {
1671     switch (status) {
1672     default:
1673     case Attendee::NeedsAction:
1674         return "NEEDS ACTION";
1675     case Attendee::Accepted:
1676         return "ACCEPTED";
1677     case Attendee::Declined:
1678         return "DECLINED";
1679     case Attendee::Tentative:
1680         return "TENTATIVE";
1681     case Attendee::Delegated:
1682         return "DELEGATED";
1683     case Attendee::Completed:
1684         return "COMPLETED";
1685     case Attendee::InProcess:
1686         return "NEEDS ACTION";
1687     }
1688 }
1689 
1690 void VCalFormat::readCustomProperties(VObject *o, const Incidence::Ptr &i)
1691 {
1692     VObjectIterator iter;
1693     char *s;
1694 
1695     initPropIterator(&iter, o);
1696     while (moreIteration(&iter)) {
1697         VObject *cur = nextVObject(&iter);
1698         const char *curname = vObjectName(cur);
1699         Q_ASSERT(curname);
1700         if ((curname[0] == 'X' && curname[1] == '-') && strcmp(curname, ICOrganizerProp) != 0) {
1701             // TODO - for the time being, we ignore the parameters part
1702             // and just do the value handling here
1703             i->setNonKDECustomProperty(curname, QString::fromUtf8(s = fakeCString(vObjectUStringZValue(cur))));
1704             deleteStr(s);
1705         }
1706     }
1707 }
1708 
1709 void VCalFormat::writeCustomProperties(VObject *o, const Incidence::Ptr &i)
1710 {
1711     Q_D(VCalFormat);
1712     const QMap<QByteArray, QString> custom = i->customProperties();
1713     for (auto cIt = custom.cbegin(); cIt != custom.cend(); ++cIt) {
1714         const QByteArray property = cIt.key();
1715         if (d->mManuallyWrittenExtensionFields.contains(property) || property.startsWith("X-KDE-VOLATILE")) { // krazy:exclude=strings
1716             continue;
1717         }
1718 
1719         addPropValue(o, property.constData(), cIt.value().toUtf8().constData());
1720     }
1721 }
1722 
1723 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 96)
1724 void VCalFormat::virtual_hook(int id, void *data)
1725 {
1726     Q_UNUSED(id);
1727     Q_UNUSED(data);
1728     Q_ASSERT(false);
1729 }
1730 #endif