File indexing completed on 2024-04-14 03:50:43

0001 /*
0002   This file is part of the kcalcore library.
0003 
0004   SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org>
0005   SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
0006   SPDX-FileCopyrightText: 2006 David Jarvie <djarvie@kde.org>
0007   SPDX-FileCopyrightText: 2012 Christian Mollekopf <mollekopf@kolabsys.com>
0008 
0009   SPDX-License-Identifier: LGPL-2.0-or-later
0010 */
0011 /**
0012   @file
0013   This file is part of the API for handling calendar data and
0014   defines the internal ICalFormat classes.
0015 
0016   @brief
0017   This class provides the libical dependent functions for ICalFormat.
0018 
0019   @author Cornelius Schumacher \<schumacher@kde.org\>
0020   @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
0021   @author David Jarvie \<djarvie@kde.org\>
0022 */
0023 
0024 #include "icalformat_p.h"
0025 #include "compat_p.h"
0026 #include "icalformat.h"
0027 #include "icaltimezones_p.h"
0028 #include "incidencebase.h"
0029 #include "memorycalendar.h"
0030 #include "visitor.h"
0031 
0032 #include "kcalendarcore_debug.h"
0033 
0034 #include <QFile>
0035 #include <QCryptographicHash>
0036 
0037 using namespace KCalendarCore;
0038 
0039 static const char APP_NAME_FOR_XPROPERTIES[] = "KCALCORE";
0040 static const char ENABLED_ALARM_XPROPERTY[] = "ENABLED";
0041 static const char IMPLEMENTATION_VERSION_XPROPERTY[] = "X-KDE-ICAL-IMPLEMENTATION-VERSION";
0042 
0043 /* Static helpers */
0044 /*
0045 static void _dumpIcaltime( const icaltimetype& t)
0046 {
0047   qCDebug(KCALCORE_LOG) << "--- Y:" << t.year << "M:" << t.month << "D:" << t.day;
0048   qCDebug(KCALCORE_LOG) << "--- H:" << t.hour << "M:" << t.minute << "S:" << t.second;
0049   qCDebug(KCALCORE_LOG) << "--- isUtc:" << icaltime_is_utc( t );
0050   qCDebug(KCALCORE_LOG) << "--- zoneId:" << icaltimezone_get_tzid( const_cast<icaltimezone*>( t.zone ) );
0051 }
0052 */
0053 
0054 //@cond PRIVATE
0055 template<typename K>
0056 void removeAllICal(QList<QSharedPointer<K>> &c, const QSharedPointer<K> &x)
0057 {
0058     if (c.count() < 1) {
0059         return;
0060     }
0061 
0062     int cnt = c.count(x);
0063     if (cnt != 1) {
0064         qCritical() << "There number of relatedTos for this incidence is " << cnt << " (there must be 1 relatedTo only)";
0065         Q_ASSERT_X(false, "removeAllICal", "Count is not 1.");
0066         return;
0067     }
0068 
0069     c.remove(c.indexOf(x));
0070 }
0071 
0072 const int gSecondsPerMinute = 60;
0073 const int gSecondsPerHour = gSecondsPerMinute * 60;
0074 const int gSecondsPerDay = gSecondsPerHour * 24;
0075 const int gSecondsPerWeek = gSecondsPerDay * 7;
0076 
0077 class ToComponentVisitor : public Visitor
0078 {
0079 public:
0080     ToComponentVisitor(ICalFormatImpl *impl, iTIPMethod m, TimeZoneList *tzUsedList = nullptr)
0081         : mImpl(impl)
0082         , mComponent(nullptr)
0083         , mMethod(m)
0084         , mTzUsedList(tzUsedList)
0085     {
0086     }
0087 
0088     ~ToComponentVisitor() override;
0089 
0090     bool visit(const Event::Ptr &e) override
0091     {
0092         mComponent = mImpl->writeEvent(e, mTzUsedList);
0093         return true;
0094     }
0095     bool visit(const Todo::Ptr &t) override
0096     {
0097         mComponent = mImpl->writeTodo(t, mTzUsedList);
0098         return true;
0099     }
0100     bool visit(const Journal::Ptr &j) override
0101     {
0102         mComponent = mImpl->writeJournal(j, mTzUsedList);
0103         return true;
0104     }
0105     bool visit(const FreeBusy::Ptr &fb) override
0106     {
0107         mComponent = mImpl->writeFreeBusy(fb, mMethod);
0108         return true;
0109     }
0110 
0111     icalcomponent *component()
0112     {
0113         return mComponent;
0114     }
0115 
0116 private:
0117     ICalFormatImpl *mImpl = nullptr;
0118     icalcomponent *mComponent = nullptr;
0119     iTIPMethod mMethod;
0120     TimeZoneList *mTzUsedList = nullptr;
0121 };
0122 
0123 ToComponentVisitor::~ToComponentVisitor()
0124 {
0125 }
0126 //@endcond
0127 
0128 inline icaltimetype ICalFormatImpl::writeICalUtcDateTime(const QDateTime &dt, bool dayOnly)
0129 {
0130     return writeICalDateTime(dt.toUTC(), dayOnly);
0131 }
0132 
0133 ICalFormatImpl::ICalFormatImpl(ICalFormat *parent)
0134     : mParent(parent)
0135     , mCompat(new Compat)
0136 {
0137 }
0138 
0139 ICalFormatImpl::~ICalFormatImpl() = default;
0140 
0141 QString ICalFormatImpl::loadedProductId() const
0142 {
0143     return mLoadedProductId;
0144 }
0145 
0146 icalcomponent *ICalFormatImpl::writeIncidence(const IncidenceBase::Ptr &incidence, iTIPMethod method, TimeZoneList *tzUsedList)
0147 {
0148     ToComponentVisitor v(this, method, tzUsedList);
0149     if (incidence->accept(v, incidence)) {
0150         return v.component();
0151     } else {
0152         return nullptr;
0153     }
0154 }
0155 
0156 icalcomponent *ICalFormatImpl::writeTodo(const Todo::Ptr &todo, TimeZoneList *tzUsedList)
0157 {
0158     icalcomponent *vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT);
0159 
0160     writeIncidence(vtodo, todo.staticCast<Incidence>(), tzUsedList);
0161 
0162     // due date
0163     icalproperty *prop;
0164     if (todo->hasDueDate()) {
0165         icaltimetype due;
0166         if (todo->allDay()) {
0167             due = writeICalDate(todo->dtDue(true).date());
0168             prop = icalproperty_new_due(due);
0169         } else {
0170             prop = writeICalDateTimeProperty(ICAL_DUE_PROPERTY, todo->dtDue(true), tzUsedList);
0171         }
0172         icalcomponent_add_property(vtodo, prop);
0173     }
0174 
0175     // start time
0176     if (todo->hasStartDate()) {
0177         icaltimetype start;
0178         if (todo->allDay()) {
0179             start = writeICalDate(todo->dtStart(true).date());
0180             prop = icalproperty_new_dtstart(start);
0181         } else {
0182             prop = writeICalDateTimeProperty(ICAL_DTSTART_PROPERTY, todo->dtStart(true), tzUsedList);
0183         }
0184         icalcomponent_add_property(vtodo, prop);
0185     }
0186 
0187     // completion date (UTC)
0188     if (todo->isCompleted()) {
0189         if (!todo->hasCompletedDate()) {
0190             // If the todo was created by KOrganizer<2.2 it does not have
0191             // a correct completion date. Set one now.
0192             todo->setCompleted(QDateTime::currentDateTimeUtc());
0193         }
0194         icaltimetype completed = writeICalUtcDateTime(todo->completed());
0195         icalcomponent_add_property(vtodo, icalproperty_new_completed(completed));
0196     }
0197 
0198     icalcomponent_add_property(vtodo, icalproperty_new_percentcomplete(todo->percentComplete()));
0199 
0200     if (todo->isCompleted()) {
0201         if (icalcomponent_count_properties(vtodo, ICAL_STATUS_PROPERTY)) {
0202             icalproperty *p = icalcomponent_get_first_property(vtodo, ICAL_STATUS_PROPERTY);
0203             icalcomponent_remove_property(vtodo, p);
0204             icalproperty_free(p);
0205         }
0206         icalcomponent_add_property(vtodo, icalproperty_new_status(ICAL_STATUS_COMPLETED));
0207     }
0208 
0209     if (todo->recurs() && todo->dtStart(false).isValid()) {
0210         prop = writeICalDateTimeProperty(ICAL_X_PROPERTY, todo->dtStart(false), tzUsedList);
0211         icalproperty_set_x_name(prop, "X-KDE-LIBKCAL-DTRECURRENCE");
0212         icalcomponent_add_property(vtodo, prop);
0213     }
0214 
0215     return vtodo;
0216 }
0217 
0218 icalcomponent *ICalFormatImpl::writeEvent(const Event::Ptr &event, TimeZoneList *tzUsedList)
0219 {
0220     icalcomponent *vevent = icalcomponent_new(ICAL_VEVENT_COMPONENT);
0221 
0222     writeIncidence(vevent, event.staticCast<Incidence>(), tzUsedList);
0223 
0224     // start time
0225     icalproperty *prop = nullptr;
0226     icaltimetype start;
0227 
0228     const QDateTime dtStart = event->dtStart();
0229     if (dtStart.isValid()) {
0230         if (event->allDay()) {
0231             start = writeICalDate(dtStart.date());
0232             prop = icalproperty_new_dtstart(start);
0233         } else {
0234             prop = writeICalDateTimeProperty(ICAL_DTSTART_PROPERTY, dtStart, tzUsedList);
0235         }
0236         icalcomponent_add_property(vevent, prop);
0237     }
0238 
0239     if (event->hasEndDate()) {
0240         // End time.
0241         // RFC2445 says that if DTEND is present, it has to be greater than DTSTART.
0242         icaltimetype end;
0243         const QDateTime dtEnd = event->dtEnd();
0244         if (event->allDay()) {
0245             // +1 day because end date is non-inclusive.
0246             end = writeICalDate(dtEnd.date().addDays(1));
0247             icalcomponent_add_property(vevent, icalproperty_new_dtend(end));
0248         } else {
0249             if (dtEnd != dtStart) {
0250                 icalcomponent_add_property(vevent, writeICalDateTimeProperty(ICAL_DTEND_PROPERTY, dtEnd, tzUsedList));
0251             }
0252         }
0253     }
0254 
0255 // TODO: resources
0256 #if 0
0257     // resources
0258     QStringList tmpStrList = anEvent->resources();
0259     QString tmpStr = tmpStrList.join(";");
0260     if (!tmpStr.isEmpty()) {
0261         addPropValue(vevent, VCResourcesProp, tmpStr.toUtf8());
0262     }
0263 
0264 #endif
0265 
0266     // Transparency
0267     switch (event->transparency()) {
0268     case Event::Transparent:
0269         icalcomponent_add_property(vevent, icalproperty_new_transp(ICAL_TRANSP_TRANSPARENT));
0270         break;
0271     case Event::Opaque:
0272         icalcomponent_add_property(vevent, icalproperty_new_transp(ICAL_TRANSP_OPAQUE));
0273         break;
0274     }
0275 
0276     return vevent;
0277 }
0278 
0279 icalcomponent *ICalFormatImpl::writeFreeBusy(const FreeBusy::Ptr &freebusy, iTIPMethod method)
0280 {
0281     icalcomponent *vfreebusy = icalcomponent_new(ICAL_VFREEBUSY_COMPONENT);
0282 
0283     writeIncidenceBase(vfreebusy, freebusy.staticCast<IncidenceBase>());
0284 
0285     icalcomponent_add_property(vfreebusy, icalproperty_new_dtstart(writeICalUtcDateTime(freebusy->dtStart())));
0286 
0287     icalcomponent_add_property(vfreebusy, icalproperty_new_dtend(writeICalUtcDateTime(freebusy->dtEnd())));
0288 
0289     Q_UNUSED(method);
0290     icalcomponent_add_property(vfreebusy, icalproperty_new_uid(freebusy->uid().toUtf8().constData()));
0291 
0292     // Loops through all the periods in the freebusy object
0293     FreeBusyPeriod::List list = freebusy->fullBusyPeriods();
0294     icalperiodtype period = icalperiodtype_null_period();
0295     for (int i = 0, count = list.count(); i < count; ++i) {
0296         const FreeBusyPeriod fbPeriod = list[i];
0297         period.start = writeICalUtcDateTime(fbPeriod.start());
0298         if (fbPeriod.hasDuration()) {
0299             period.duration = writeICalDuration(fbPeriod.duration());
0300         } else {
0301             period.end = writeICalUtcDateTime(fbPeriod.end());
0302         }
0303 
0304         icalproperty *property = icalproperty_new_freebusy(period);
0305 
0306         icalparameter_fbtype fbType;
0307         switch (fbPeriod.type()) {
0308         case FreeBusyPeriod::Free:
0309             fbType = ICAL_FBTYPE_FREE;
0310             break;
0311         case FreeBusyPeriod::Busy:
0312             fbType = ICAL_FBTYPE_BUSY;
0313             break;
0314         case FreeBusyPeriod::BusyTentative:
0315             fbType = ICAL_FBTYPE_BUSYTENTATIVE;
0316             break;
0317         case FreeBusyPeriod::BusyUnavailable:
0318             fbType = ICAL_FBTYPE_BUSYUNAVAILABLE;
0319             break;
0320         case FreeBusyPeriod::Unknown:
0321             fbType = ICAL_FBTYPE_X;
0322             break;
0323         default:
0324             fbType = ICAL_FBTYPE_NONE;
0325             break;
0326         }
0327         icalproperty_set_parameter(property, icalparameter_new_fbtype(fbType));
0328 
0329         if (!fbPeriod.summary().isEmpty()) {
0330             icalparameter *param = icalparameter_new_x("X-SUMMARY");
0331             icalparameter_set_xvalue(param, fbPeriod.summary().toUtf8().toBase64().constData());
0332             icalproperty_set_parameter(property, param);
0333         }
0334         if (!fbPeriod.location().isEmpty()) {
0335             icalparameter *param = icalparameter_new_x("X-LOCATION");
0336             icalparameter_set_xvalue(param, fbPeriod.location().toUtf8().toBase64().constData());
0337             icalproperty_set_parameter(property, param);
0338         }
0339 
0340         icalcomponent_add_property(vfreebusy, property);
0341     }
0342 
0343     return vfreebusy;
0344 }
0345 
0346 icalcomponent *ICalFormatImpl::writeJournal(const Journal::Ptr &journal, TimeZoneList *tzUsedList)
0347 {
0348     icalcomponent *vjournal = icalcomponent_new(ICAL_VJOURNAL_COMPONENT);
0349 
0350     writeIncidence(vjournal, journal.staticCast<Incidence>(), tzUsedList);
0351 
0352     // start time
0353     icalproperty *prop = nullptr;
0354     QDateTime dt = journal->dtStart();
0355     if (dt.isValid()) {
0356         icaltimetype start;
0357         if (journal->allDay()) {
0358             start = writeICalDate(dt.date());
0359             prop = icalproperty_new_dtstart(start);
0360         } else {
0361             prop = writeICalDateTimeProperty(ICAL_DTSTART_PROPERTY, dt, tzUsedList);
0362         }
0363         icalcomponent_add_property(vjournal, prop);
0364     }
0365 
0366     return vjournal;
0367 }
0368 
0369 void ICalFormatImpl::writeIncidence(icalcomponent *parent, const Incidence::Ptr &incidence, TimeZoneList *tzUsedList)
0370 {
0371     if (incidence->schedulingID() != incidence->uid()) {
0372         // We need to store the UID in here. The rawSchedulingID will
0373         // go into the iCal UID component
0374         incidence->setCustomProperty("LIBKCAL", "ID", incidence->uid());
0375     } else {
0376         incidence->removeCustomProperty("LIBKCAL", "ID");
0377     }
0378 
0379     writeIncidenceBase(parent, incidence.staticCast<IncidenceBase>());
0380 
0381     // creation date in storage
0382     icalcomponent_add_property(parent, writeICalDateTimeProperty(ICAL_CREATED_PROPERTY, incidence->created()));
0383 
0384     // unique id
0385     // If the scheduling ID is different from the real UID, the real
0386     // one is stored on X-REALID above
0387     if (!incidence->schedulingID().isEmpty()) {
0388         icalcomponent_add_property(parent, icalproperty_new_uid(incidence->schedulingID().toUtf8().constData()));
0389     }
0390 
0391     // revision
0392     if (incidence->revision() > 0) { // 0 is default, so don't write that out
0393         icalcomponent_add_property(parent, icalproperty_new_sequence(incidence->revision()));
0394     }
0395 
0396     // last modification date
0397     if (incidence->lastModified().isValid()) {
0398         icalcomponent_add_property(parent, writeICalDateTimeProperty(ICAL_LASTMODIFIED_PROPERTY, incidence->lastModified()));
0399     }
0400 
0401     // description
0402     if (!incidence->description().isEmpty()) {
0403         icalcomponent_add_property(parent, writeDescription(incidence->description(), incidence->descriptionIsRich()));
0404     }
0405 
0406     // summary
0407     if (!incidence->summary().isEmpty()) {
0408         icalcomponent_add_property(parent, writeSummary(incidence->summary(), incidence->summaryIsRich()));
0409     }
0410 
0411     // location
0412     if (!incidence->location().isEmpty()) {
0413         icalcomponent_add_property(parent, writeLocation(incidence->location(), incidence->locationIsRich()));
0414     }
0415 
0416     // status
0417     icalproperty_status status = ICAL_STATUS_NONE;
0418     switch (incidence->status()) {
0419     case Incidence::StatusTentative:
0420         status = ICAL_STATUS_TENTATIVE;
0421         break;
0422     case Incidence::StatusConfirmed:
0423         status = ICAL_STATUS_CONFIRMED;
0424         break;
0425     case Incidence::StatusCompleted:
0426         status = ICAL_STATUS_COMPLETED;
0427         break;
0428     case Incidence::StatusNeedsAction:
0429         status = ICAL_STATUS_NEEDSACTION;
0430         break;
0431     case Incidence::StatusCanceled:
0432         status = ICAL_STATUS_CANCELLED;
0433         break;
0434     case Incidence::StatusInProcess:
0435         status = ICAL_STATUS_INPROCESS;
0436         break;
0437     case Incidence::StatusDraft:
0438         status = ICAL_STATUS_DRAFT;
0439         break;
0440     case Incidence::StatusFinal:
0441         status = ICAL_STATUS_FINAL;
0442         break;
0443     case Incidence::StatusX: {
0444         icalproperty *p = icalproperty_new_status(ICAL_STATUS_X);
0445         icalvalue_set_x(icalproperty_get_value(p), incidence->customStatus().toUtf8().constData());
0446         icalcomponent_add_property(parent, p);
0447         break;
0448     }
0449     case Incidence::StatusNone:
0450     default:
0451         break;
0452     }
0453     if (status != ICAL_STATUS_NONE) {
0454         icalcomponent_add_property(parent, icalproperty_new_status(status));
0455     }
0456 
0457     // secrecy
0458     icalproperty_class secClass;
0459     switch (incidence->secrecy()) {
0460     case Incidence::SecrecyPublic:
0461         secClass = ICAL_CLASS_PUBLIC;
0462         break;
0463     case Incidence::SecrecyConfidential:
0464         secClass = ICAL_CLASS_CONFIDENTIAL;
0465         break;
0466     case Incidence::SecrecyPrivate:
0467     default:
0468         secClass = ICAL_CLASS_PRIVATE;
0469         break;
0470     }
0471     if (secClass != ICAL_CLASS_PUBLIC) {
0472         icalcomponent_add_property(parent, icalproperty_new_class(secClass));
0473     }
0474 
0475     // color
0476     if (!incidence->color().isEmpty()) {
0477         icalcomponent_add_property(parent, icalproperty_new_color(incidence->color().toUtf8().constData()));
0478     }
0479 
0480     // geo
0481     if (incidence->hasGeo()) {
0482         icalgeotype geo;
0483         geo.lat = incidence->geoLatitude();
0484         geo.lon = incidence->geoLongitude();
0485         icalcomponent_add_property(parent, icalproperty_new_geo(geo));
0486     }
0487 
0488     // priority
0489     if (incidence->priority() > 0) { // 0 is undefined priority
0490         icalcomponent_add_property(parent, icalproperty_new_priority(incidence->priority()));
0491     }
0492 
0493     // categories
0494     QString categories = incidence->categories().join(QLatin1Char(','));
0495     if (!categories.isEmpty()) {
0496         icalcomponent_add_property(parent, icalproperty_new_categories(categories.toUtf8().constData()));
0497     }
0498 
0499     // related event
0500     if (!incidence->relatedTo().isEmpty()) {
0501         icalcomponent_add_property(parent, icalproperty_new_relatedto(incidence->relatedTo().toUtf8().constData()));
0502     }
0503 
0504     // recurrenceid
0505     if (incidence->hasRecurrenceId()) {
0506         icalproperty *p = writeICalDateTimeProperty(ICAL_RECURRENCEID_PROPERTY, incidence->recurrenceId(), tzUsedList);
0507         if (incidence->thisAndFuture()) {
0508             icalproperty_add_parameter(p, icalparameter_new_range(ICAL_RANGE_THISANDFUTURE));
0509         }
0510         icalcomponent_add_property(parent, p);
0511     }
0512 
0513     const RecurrenceRule::List rrules(incidence->recurrence()->rRules());
0514     for (RecurrenceRule *rule : rrules) {
0515         icalcomponent_add_property(parent, icalproperty_new_rrule(writeRecurrenceRule(rule)));
0516     }
0517 
0518     const RecurrenceRule::List exrules(incidence->recurrence()->exRules());
0519     for (RecurrenceRule *rule : exrules) {
0520         icalcomponent_add_property(parent, icalproperty_new_exrule(writeRecurrenceRule(rule)));
0521     }
0522 
0523     DateList dateList = incidence->recurrence()->exDates();
0524     for (const auto &date : std::as_const(dateList)) {
0525         icalcomponent_add_property(parent, icalproperty_new_exdate(writeICalDate(date)));
0526     }
0527 
0528     auto dateTimeList = incidence->recurrence()->exDateTimes();
0529     for (const auto &dt : std::as_const(dateTimeList)) {
0530         icalcomponent_add_property(parent, writeICalDateTimeProperty(ICAL_EXDATE_PROPERTY, dt, tzUsedList));
0531     }
0532 
0533     dateList = incidence->recurrence()->rDates();
0534     for (const auto &date : std::as_const(dateList)) {
0535         icalcomponent_add_property(parent, icalproperty_new_rdate(writeICalDatePeriod(date)));
0536     }
0537     dateTimeList = incidence->recurrence()->rDateTimes();
0538     for (const auto &dt : std::as_const(dateTimeList)) {
0539         Period period = incidence->recurrence()->rDateTimePeriod(dt);
0540         if (period.isValid()) {
0541             icaldatetimeperiodtype tp;
0542             tp.time = icaltime_null_time();
0543             tp.period = icalperiodtype_null_period();
0544             tp.period.start = writeICalDateTime(period.start());
0545             if (period.hasDuration()) {
0546                 tp.period.duration = writeICalDuration(period.duration());
0547             } else {
0548                 tp.period.end = writeICalDateTime(period.end());
0549             }
0550             icalcomponent_add_property(parent, icalproperty_new_rdate(tp));
0551         } else {
0552             icalcomponent_add_property(parent, writeICalDateTimeProperty(ICAL_RDATE_PROPERTY, dt, tzUsedList));
0553         }
0554     }
0555 
0556     // attachments
0557     const Attachment::List attachments = incidence->attachments();
0558     for (const auto &at : attachments) {
0559         icalcomponent_add_property(parent, writeAttachment(at));
0560     }
0561 
0562     // alarms
0563     auto alarms = incidence->alarms();
0564     for (auto it = alarms.cbegin(), end = alarms.cend(); it != end; ++it) {
0565         icalcomponent_add_component(parent, writeAlarm(*it));
0566     }
0567 
0568     // conferences
0569     const auto conferences = incidence->conferences();
0570     for (const auto &conf : conferences) {
0571         icalcomponent_add_property(parent, writeConference(conf));
0572     }
0573 
0574     // duration
0575     if (incidence->hasDuration()) {
0576         icaldurationtype duration;
0577         duration = writeICalDuration(incidence->duration());
0578         icalcomponent_add_property(parent, icalproperty_new_duration(duration));
0579     }
0580 }
0581 
0582 //@cond PRIVATE
0583 void ICalFormatImpl::writeIncidenceBase(icalcomponent *parent, const IncidenceBase::Ptr &incidenceBase)
0584 {
0585     // organizer stuff
0586     if (!incidenceBase->organizer().isEmpty()) {
0587         icalproperty *p = writeOrganizer(incidenceBase->organizer());
0588         if (p) {
0589             icalcomponent_add_property(parent, p);
0590         }
0591     }
0592 
0593     icalcomponent_add_property(parent, icalproperty_new_dtstamp(writeICalUtcDateTime(incidenceBase->lastModified())));
0594 
0595     // attendees
0596     if (incidenceBase->attendeeCount() > 0) {
0597         auto attendees = incidenceBase->attendees();
0598         for (auto it = attendees.constBegin(); it != attendees.constEnd(); ++it) {
0599             icalproperty *p = writeAttendee(*it);
0600             if (p) {
0601                 icalcomponent_add_property(parent, p);
0602             }
0603         }
0604     }
0605 
0606     // contacts
0607     const QStringList contacts = incidenceBase->contacts();
0608     for (const auto &contact : contacts) {
0609         icalcomponent_add_property(parent, icalproperty_new_contact(contact.toUtf8().constData()));
0610     }
0611 
0612     // comments
0613     const QStringList comments = incidenceBase->comments();
0614     for (const auto &comment : comments) {
0615         icalcomponent_add_property(parent, icalproperty_new_comment(comment.toUtf8().constData()));
0616     }
0617 
0618     // url
0619     const QUrl url = incidenceBase->url();
0620     if (url.isValid()) {
0621         icalcomponent_add_property(parent, icalproperty_new_url(url.toString().toUtf8().constData()));
0622     }
0623 
0624     // custom properties
0625     writeCustomProperties(parent, incidenceBase.data());
0626 }
0627 
0628 void ICalFormatImpl::writeCustomProperties(icalcomponent *parent, CustomProperties *properties)
0629 {
0630     const QMap<QByteArray, QString> custom = properties->customProperties();
0631     for (auto c = custom.cbegin(); c != custom.cend(); ++c) {
0632         if (c.key().startsWith("X-KDE-VOLATILE")) { // krazy:exclude=strings
0633             // We don't write these properties to disk to disk
0634             continue;
0635         }
0636         icalproperty *p = icalproperty_new_x(c.value().toUtf8().constData());
0637         QString parameters = properties->nonKDECustomPropertyParameters(c.key());
0638 
0639         // Minimalist parameter handler: extract icalparameter's out of
0640         // the given input text (not really parsing as such)
0641         if (!parameters.isEmpty()) {
0642             const QStringList sl = parameters.split(QLatin1Char(';'));
0643             for (const QString &parameter : sl) {
0644                 icalparameter *param = icalparameter_new_from_string(parameter.toUtf8().constData());
0645                 if (param) {
0646                     icalproperty_add_parameter(p, param);
0647                 }
0648             }
0649         }
0650 
0651         icalproperty_set_x_name(p, c.key().constData());
0652         icalcomponent_add_property(parent, p);
0653     }
0654 }
0655 //@endcond
0656 
0657 icalproperty *ICalFormatImpl::writeOrganizer(const Person &organizer)
0658 {
0659     if (organizer.email().isEmpty()) {
0660         return nullptr;
0661     }
0662 
0663     icalproperty *p = icalproperty_new_organizer(QByteArray(QByteArray("MAILTO:") + organizer.email().toUtf8()).constData());
0664 
0665     if (!organizer.name().isEmpty()) {
0666         icalproperty_add_parameter(p,
0667                                    icalparameter_new_cn(organizer.name().toUtf8().constData()));
0668     }
0669     // TODO: Write dir, sent-by and language
0670 
0671     return p;
0672 }
0673 
0674 icalproperty *ICalFormatImpl::writeDescription(const QString &description, bool isRich)
0675 {
0676     icalproperty *p = icalproperty_new_description(description.toUtf8().constData());
0677     if (isRich) {
0678         icalproperty_add_parameter(p, icalparameter_new_from_string("X-KDE-TEXTFORMAT=HTML"));
0679     }
0680     return p;
0681 }
0682 
0683 icalproperty *ICalFormatImpl::writeSummary(const QString &summary, bool isRich)
0684 {
0685     icalproperty *p = icalproperty_new_summary(summary.toUtf8().constData());
0686     if (isRich) {
0687         icalproperty_add_parameter(p, icalparameter_new_from_string("X-KDE-TEXTFORMAT=HTML"));
0688     }
0689     return p;
0690 }
0691 
0692 icalproperty *ICalFormatImpl::writeLocation(const QString &location, bool isRich)
0693 {
0694     icalproperty *p = icalproperty_new_location(location.toUtf8().constData());
0695     if (isRich) {
0696         icalproperty_add_parameter(p, icalparameter_new_from_string("X-KDE-TEXTFORMAT=HTML"));
0697     }
0698     return p;
0699 }
0700 
0701 icalproperty *ICalFormatImpl::writeAttendee(const Attendee &attendee)
0702 {
0703     if (attendee.email().isEmpty()) {
0704         return nullptr;
0705     }
0706 
0707     icalproperty *p = icalproperty_new_attendee(QByteArray(QByteArray("mailto:") + attendee.email().toUtf8()).constData());
0708 
0709     if (!attendee.name().isEmpty()) {
0710         icalproperty_add_parameter(p,
0711                                    icalparameter_new_cn(attendee.name().toUtf8().constData()));
0712     }
0713 
0714     icalproperty_add_parameter(p, icalparameter_new_rsvp(attendee.RSVP() ? ICAL_RSVP_TRUE : ICAL_RSVP_FALSE));
0715 
0716     icalparameter_partstat status = ICAL_PARTSTAT_NEEDSACTION;
0717     switch (attendee.status()) {
0718     default:
0719     case Attendee::NeedsAction:
0720         status = ICAL_PARTSTAT_NEEDSACTION;
0721         break;
0722     case Attendee::Accepted:
0723         status = ICAL_PARTSTAT_ACCEPTED;
0724         break;
0725     case Attendee::Declined:
0726         status = ICAL_PARTSTAT_DECLINED;
0727         break;
0728     case Attendee::Tentative:
0729         status = ICAL_PARTSTAT_TENTATIVE;
0730         break;
0731     case Attendee::Delegated:
0732         status = ICAL_PARTSTAT_DELEGATED;
0733         break;
0734     case Attendee::Completed:
0735         status = ICAL_PARTSTAT_COMPLETED;
0736         break;
0737     case Attendee::InProcess:
0738         status = ICAL_PARTSTAT_INPROCESS;
0739         break;
0740     }
0741     icalproperty_add_parameter(p, icalparameter_new_partstat(status));
0742 
0743     icalparameter_role role = ICAL_ROLE_REQPARTICIPANT;
0744     switch (attendee.role()) {
0745     case Attendee::Chair:
0746         role = ICAL_ROLE_CHAIR;
0747         break;
0748     default:
0749     case Attendee::ReqParticipant:
0750         role = ICAL_ROLE_REQPARTICIPANT;
0751         break;
0752     case Attendee::OptParticipant:
0753         role = ICAL_ROLE_OPTPARTICIPANT;
0754         break;
0755     case Attendee::NonParticipant:
0756         role = ICAL_ROLE_NONPARTICIPANT;
0757         break;
0758     }
0759     icalproperty_add_parameter(p, icalparameter_new_role(role));
0760 
0761     icalparameter_cutype cutype = ICAL_CUTYPE_INDIVIDUAL;
0762     switch (attendee.cuType()) {
0763     case Attendee::Unknown:
0764         cutype = ICAL_CUTYPE_UNKNOWN;
0765         break;
0766     default:
0767     case Attendee::Individual:
0768         cutype = ICAL_CUTYPE_INDIVIDUAL;
0769         break;
0770     case Attendee::Group:
0771         cutype = ICAL_CUTYPE_GROUP;
0772         break;
0773     case Attendee::Resource:
0774         cutype = ICAL_CUTYPE_RESOURCE;
0775         break;
0776     case Attendee::Room:
0777         cutype = ICAL_CUTYPE_ROOM;
0778         break;
0779     }
0780     icalproperty_add_parameter(p, icalparameter_new_cutype(cutype));
0781 
0782     if (!attendee.uid().isEmpty()) {
0783         icalparameter *icalparameter_uid = icalparameter_new_x(attendee.uid().toUtf8().constData());
0784 
0785         icalparameter_set_xname(icalparameter_uid, "X-UID");
0786         icalproperty_add_parameter(p, icalparameter_uid);
0787     }
0788 
0789     if (!attendee.delegate().isEmpty()) {
0790         icalparameter *icalparameter_delegate = icalparameter_new_delegatedto(attendee.delegate().toUtf8().constData());
0791         icalproperty_add_parameter(p, icalparameter_delegate);
0792     }
0793 
0794     if (!attendee.delegator().isEmpty()) {
0795         icalparameter *icalparameter_delegator = icalparameter_new_delegatedfrom(attendee.delegator().toUtf8().constData());
0796         icalproperty_add_parameter(p, icalparameter_delegator);
0797     }
0798 
0799     return p;
0800 }
0801 
0802 icalproperty *ICalFormatImpl::writeAttachment(const Attachment &att)
0803 {
0804     icalattach *attach;
0805     if (att.isUri()) {
0806         attach = icalattach_new_from_url(att.uri().toUtf8().data());
0807     } else {
0808         attach = icalattach_new_from_data((const char *)att.data().constData(), nullptr, nullptr);
0809     }
0810     icalproperty *p = icalproperty_new_attach(attach);
0811 
0812     icalattach_unref(attach);
0813 
0814     if (!att.mimeType().isEmpty()) {
0815         icalproperty_add_parameter(p, icalparameter_new_fmttype(att.mimeType().toUtf8().data()));
0816     }
0817 
0818     if (att.isBinary()) {
0819         icalproperty_add_parameter(p, icalparameter_new_value(ICAL_VALUE_BINARY));
0820         icalproperty_add_parameter(p, icalparameter_new_encoding(ICAL_ENCODING_BASE64));
0821     }
0822 
0823     if (att.showInline()) {
0824         icalparameter *icalparameter_inline = icalparameter_new_x("inline");
0825         icalparameter_set_xname(icalparameter_inline, "X-CONTENT-DISPOSITION");
0826         icalproperty_add_parameter(p, icalparameter_inline);
0827     }
0828 
0829     if (!att.label().isEmpty()) {
0830         icalparameter *icalparameter_label = icalparameter_new_x(att.label().toUtf8().constData());
0831         icalparameter_set_xname(icalparameter_label, "X-LABEL");
0832         icalproperty_add_parameter(p, icalparameter_label);
0833     }
0834 
0835     if (att.isLocal()) {
0836         icalparameter *icalparameter_local = icalparameter_new_x("local");
0837         icalparameter_set_xname(icalparameter_local, "X-KONTACT-TYPE");
0838         icalproperty_add_parameter(p, icalparameter_local);
0839     }
0840 
0841     return p;
0842 }
0843 
0844 icalrecurrencetype ICalFormatImpl::writeRecurrenceRule(RecurrenceRule *recur)
0845 {
0846     icalrecurrencetype r;
0847     icalrecurrencetype_clear(&r);
0848 
0849     switch (recur->recurrenceType()) {
0850     case RecurrenceRule::rSecondly:
0851         r.freq = ICAL_SECONDLY_RECURRENCE;
0852         break;
0853     case RecurrenceRule::rMinutely:
0854         r.freq = ICAL_MINUTELY_RECURRENCE;
0855         break;
0856     case RecurrenceRule::rHourly:
0857         r.freq = ICAL_HOURLY_RECURRENCE;
0858         break;
0859     case RecurrenceRule::rDaily:
0860         r.freq = ICAL_DAILY_RECURRENCE;
0861         break;
0862     case RecurrenceRule::rWeekly:
0863         r.freq = ICAL_WEEKLY_RECURRENCE;
0864         break;
0865     case RecurrenceRule::rMonthly:
0866         r.freq = ICAL_MONTHLY_RECURRENCE;
0867         break;
0868     case RecurrenceRule::rYearly:
0869         r.freq = ICAL_YEARLY_RECURRENCE;
0870         break;
0871     default:
0872         r.freq = ICAL_NO_RECURRENCE;
0873         qCDebug(KCALCORE_LOG) << "no recurrence";
0874         break;
0875     }
0876 
0877     int index = 0;
0878     QList<int> bys;
0879 
0880     // Now write out the BY* parts:
0881     bys = recur->bySeconds();
0882     index = 0;
0883     for (auto it = bys.constBegin(); it != bys.constEnd(); ++it) {
0884         r.by_second[index++] = *it;
0885         r.by_second[index++] = static_cast<short>(*it);
0886     }
0887 
0888     bys = recur->byMinutes();
0889     index = 0;
0890     for (auto it = bys.constBegin(); it != bys.constEnd(); ++it) {
0891         r.by_minute[index++] = *it;
0892         r.by_minute[index++] = static_cast<short>(*it);
0893     }
0894 
0895     bys = recur->byHours();
0896     index = 0;
0897     for (auto it = bys.constBegin(); it != bys.constEnd(); ++it) {
0898         r.by_hour[index++] = *it;
0899         r.by_hour[index++] = static_cast<short>(*it);
0900     }
0901 
0902     bys = recur->byMonthDays();
0903     index = 0;
0904     for (auto it = bys.constBegin(); it != bys.constEnd(); ++it) {
0905         short dShort = static_cast<short>((*it) * 8);
0906         r.by_month_day[index++] = static_cast<short>(icalrecurrencetype_day_position(dShort));
0907     }
0908 
0909     bys = recur->byYearDays();
0910     index = 0;
0911     for (auto it = bys.constBegin(); it != bys.constEnd(); ++it) {
0912         r.by_year_day[index++] = static_cast<short>(*it);
0913     }
0914 
0915     bys = recur->byWeekNumbers();
0916     index = 0;
0917     for (auto it = bys.constBegin(); it != bys.constEnd(); ++it) {
0918         r.by_week_no[index++] = static_cast<short>(*it);
0919     }
0920 
0921     bys = recur->byMonths();
0922     index = 0;
0923     for (auto it = bys.constBegin(); it != bys.constEnd(); ++it) {
0924         r.by_month[index++] = static_cast<short>(*it);
0925     }
0926 
0927     bys = recur->bySetPos();
0928     index = 0;
0929     for (auto it = bys.constBegin(); it != bys.constEnd(); ++it) {
0930         r.by_set_pos[index++] = static_cast<short>(*it);
0931     }
0932 
0933     const QList<RecurrenceRule::WDayPos> &byd = recur->byDays();
0934     int day;
0935     index = 0;
0936     for (auto it = byd.cbegin(); it != byd.cend(); ++it) {
0937         const auto &reRule = *it;
0938         day = (reRule.day() % 7) + 1; // convert from Monday=1 to Sunday=1
0939         if (reRule.pos() < 0) {
0940             day += (-reRule.pos()) * 8;
0941             day = -day;
0942         } else {
0943             day += reRule.pos() * 8;
0944         }
0945         r.by_day[index++] = static_cast<short>(day);
0946     }
0947 
0948     r.week_start = static_cast<icalrecurrencetype_weekday>(recur->weekStart() % 7 + 1);
0949 
0950     if (recur->frequency() > 1) {
0951         // Don't write out INTERVAL=1, because that's the default anyway
0952         r.interval = static_cast<short>(recur->frequency());
0953     }
0954 
0955     if (recur->duration() > 0) {
0956         r.count = recur->duration();
0957     } else if (recur->duration() == -1) {
0958         r.count = 0;
0959     } else {
0960         if (recur->allDay()) {
0961             r.until = writeICalDate(recur->endDt().date());
0962         } else {
0963             r.until = writeICalUtcDateTime(recur->endDt());
0964         }
0965     }
0966 
0967     return r;
0968 }
0969 
0970 icalcomponent *ICalFormatImpl::writeAlarm(const Alarm::Ptr &alarm)
0971 {
0972     if (alarm->enabled()) {
0973         alarm->setCustomProperty(APP_NAME_FOR_XPROPERTIES, ENABLED_ALARM_XPROPERTY, QStringLiteral("TRUE"));
0974     } else {
0975         alarm->setCustomProperty(APP_NAME_FOR_XPROPERTIES, ENABLED_ALARM_XPROPERTY, QStringLiteral("FALSE"));
0976     }
0977 
0978     icalcomponent *a = icalcomponent_new(ICAL_VALARM_COMPONENT);
0979 
0980     icalproperty_action action;
0981     icalattach *attach = nullptr;
0982 
0983     switch (alarm->type()) {
0984     case Alarm::Procedure:
0985         action = ICAL_ACTION_PROCEDURE;
0986         attach = icalattach_new_from_url(QFile::encodeName(alarm->programFile()).data());
0987         icalcomponent_add_property(a, icalproperty_new_attach(attach));
0988         if (!alarm->programArguments().isEmpty()) {
0989             icalcomponent_add_property(a, icalproperty_new_description(alarm->programArguments().toUtf8().constData()));
0990         }
0991         break;
0992     case Alarm::Audio:
0993         action = ICAL_ACTION_AUDIO;
0994         if (!alarm->audioFile().isEmpty()) {
0995             attach = icalattach_new_from_url(QFile::encodeName(alarm->audioFile()).data());
0996             icalcomponent_add_property(a, icalproperty_new_attach(attach));
0997         }
0998         break;
0999     case Alarm::Email: {
1000         action = ICAL_ACTION_EMAIL;
1001         const Person::List addresses = alarm->mailAddresses();
1002         for (const auto &ad : addresses) {
1003             if (!ad.email().isEmpty()) {
1004                 icalproperty *p = icalproperty_new_attendee(QByteArray(QByteArray("MAILTO:") + ad.email().toUtf8()).constData());
1005                 if (!ad.name().isEmpty()) {
1006                     icalproperty_add_parameter(p, icalparameter_new_cn(ad.name().toUtf8().constData()));
1007                 }
1008                 icalcomponent_add_property(a, p);
1009             }
1010         }
1011         icalcomponent_add_property(a, icalproperty_new_summary(alarm->mailSubject().toUtf8().constData()));
1012         icalcomponent_add_property(a, icalproperty_new_description(alarm->mailText().toUtf8().constData()));
1013         const QStringList attachments = alarm->mailAttachments();
1014         if (!attachments.isEmpty()) {
1015             for (const auto &at : attachments) {
1016                 attach = icalattach_new_from_url(QFile::encodeName(at).constData());
1017                 icalcomponent_add_property(a, icalproperty_new_attach(attach));
1018             }
1019         }
1020         break;
1021     }
1022     case Alarm::Display:
1023         action = ICAL_ACTION_DISPLAY;
1024         icalcomponent_add_property(a, icalproperty_new_description(alarm->text().toUtf8().constData()));
1025         break;
1026     case Alarm::Invalid:
1027     default:
1028         qCDebug(KCALCORE_LOG) << "Unknown type of alarm";
1029         action = ICAL_ACTION_NONE;
1030         break;
1031     }
1032     icalcomponent_add_property(a, icalproperty_new_action(action));
1033 
1034     // Trigger time
1035     icaltriggertype trigger;
1036     if (alarm->hasTime()) {
1037         trigger.time = writeICalUtcDateTime(alarm->time(), false);
1038         trigger.duration = icaldurationtype_null_duration();
1039     } else {
1040         trigger.time = icaltime_null_time();
1041         Duration offset;
1042         if (alarm->hasStartOffset()) {
1043             offset = alarm->startOffset();
1044         } else {
1045             offset = alarm->endOffset();
1046         }
1047         trigger.duration = writeICalDuration(offset);
1048     }
1049     icalproperty *p = icalproperty_new_trigger(trigger);
1050     if (alarm->hasEndOffset()) {
1051         icalproperty_add_parameter(p, icalparameter_new_related(ICAL_RELATED_END));
1052     }
1053     icalcomponent_add_property(a, p);
1054 
1055     // Repeat count and duration
1056     if (alarm->repeatCount()) {
1057         icalcomponent_add_property(a, icalproperty_new_repeat(alarm->repeatCount()));
1058         icalcomponent_add_property(a, icalproperty_new_duration(writeICalDuration(alarm->snoozeTime())));
1059     }
1060 
1061     // Custom properties
1062     const QMap<QByteArray, QString> custom = alarm->customProperties();
1063     for (auto c = custom.cbegin(); c != custom.cend(); ++c) {
1064         icalproperty *p = icalproperty_new_x(c.value().toUtf8().constData());
1065         icalproperty_set_x_name(p, c.key().constData());
1066         icalcomponent_add_property(a, p);
1067     }
1068 
1069     icalattach_unref(attach);
1070 
1071     return a;
1072 }
1073 
1074 icalproperty *ICalFormatImpl::writeConference(const Conference &conference)
1075 {
1076     icalproperty *p = icalproperty_new_conference(conference.uri().toString().toUtf8().constData());
1077     icalproperty_set_parameter_from_string(p, "VALUE", "URI");
1078     icalproperty_set_parameter_from_string(p, "FEATURE", conference.features().join(QLatin1Char(',')).toUtf8().constData());
1079     icalproperty_set_parameter_from_string(p, "LABEL", conference.label().toUtf8().constData());
1080 
1081     return p;
1082 }
1083 
1084 Todo::Ptr ICalFormatImpl::readTodo(icalcomponent *vtodo, const ICalTimeZoneCache *tzlist)
1085 {
1086     Todo::Ptr todo(new Todo);
1087 
1088     readIncidence(vtodo, todo, tzlist);
1089 
1090     icalproperty *p = icalcomponent_get_first_property(vtodo, ICAL_ANY_PROPERTY);
1091 
1092     while (p) {
1093         icalproperty_kind kind = icalproperty_isa(p);
1094         switch (kind) {
1095         case ICAL_DUE_PROPERTY: {
1096             // due date/time
1097             bool allDay = false;
1098             QDateTime kdt = readICalDateTimeProperty(p, tzlist, false, &allDay);
1099             todo->setDtDue(kdt, true);
1100             todo->setAllDay(allDay);
1101             break;
1102         }
1103         case ICAL_COMPLETED_PROPERTY: // completion date/time
1104             todo->setCompleted(readICalDateTimeProperty(p, tzlist));
1105             break;
1106 
1107         case ICAL_PERCENTCOMPLETE_PROPERTY: // Percent completed
1108             todo->setPercentComplete(icalproperty_get_percentcomplete(p));
1109             break;
1110 
1111         case ICAL_RELATEDTO_PROPERTY: // related todo (parent)
1112             todo->setRelatedTo(QString::fromUtf8(icalproperty_get_relatedto(p)));
1113             mTodosRelate.append(todo);
1114             break;
1115 
1116         case ICAL_DTSTART_PROPERTY:
1117             // Flag that todo has start date. Value is read in by readIncidence().
1118             if (!todo->comments().filter(QStringLiteral("NoStartDate")).isEmpty()) {
1119                 todo->setDtStart(QDateTime());
1120             }
1121             break;
1122         case ICAL_X_PROPERTY: {
1123             const char *name = icalproperty_get_x_name(p);
1124             if (QLatin1String(name) == QLatin1String("X-KDE-LIBKCAL-DTRECURRENCE")) {
1125                 const QDateTime dateTime = readICalDateTimeProperty(p, tzlist);
1126                 if (dateTime.isValid()) {
1127                     todo->setDtRecurrence(dateTime);
1128                 } else {
1129                     qCDebug(KCALCORE_LOG) << "Invalid dateTime";
1130                 }
1131             }
1132         } break;
1133         default:
1134             // TODO: do something about unknown properties?
1135             break;
1136         }
1137 
1138         p = icalcomponent_get_next_property(vtodo, ICAL_ANY_PROPERTY);
1139     }
1140 
1141     if (mCompat) {
1142         mCompat->fixEmptySummary(todo);
1143     }
1144 
1145     todo->resetDirtyFields();
1146     return todo;
1147 }
1148 
1149 Event::Ptr ICalFormatImpl::readEvent(icalcomponent *vevent, const ICalTimeZoneCache *tzlist)
1150 {
1151     Event::Ptr event(new Event);
1152 
1153     readIncidence(vevent, event, tzlist);
1154 
1155     icalproperty *p = icalcomponent_get_first_property(vevent, ICAL_ANY_PROPERTY);
1156 
1157     bool dtEndProcessed = false;
1158 
1159     while (p) {
1160         icalproperty_kind kind = icalproperty_isa(p);
1161         switch (kind) {
1162         case ICAL_DTEND_PROPERTY: {
1163             // end date and time
1164             bool allDay = false;
1165             QDateTime kdt = readICalDateTimeProperty(p, tzlist, false, &allDay);
1166             if (allDay) {
1167                 // End date is non-inclusive
1168                 QDate endDate = kdt.date().addDays(-1);
1169                 if (mCompat) {
1170                     mCompat->fixFloatingEnd(endDate);
1171                 }
1172                 if (endDate < event->dtStart().date()) {
1173                     endDate = event->dtStart().date();
1174                 }
1175                 event->setDtEnd(QDateTime(endDate, {}, QTimeZone::LocalTime));
1176                 event->setAllDay(true);
1177             } else {
1178                 event->setDtEnd(kdt);
1179                 event->setAllDay(false);
1180             }
1181             dtEndProcessed = true;
1182             break;
1183         }
1184         case ICAL_RELATEDTO_PROPERTY: // related event (parent)
1185             event->setRelatedTo(QString::fromUtf8(icalproperty_get_relatedto(p)));
1186             mEventsRelate.append(event);
1187             break;
1188 
1189         case ICAL_TRANSP_PROPERTY: { // Transparency
1190             icalproperty_transp transparency = icalproperty_get_transp(p);
1191             if (transparency == ICAL_TRANSP_TRANSPARENT) {
1192                 event->setTransparency(Event::Transparent);
1193             } else {
1194                 event->setTransparency(Event::Opaque);
1195             }
1196             break;
1197         }
1198 
1199         default:
1200             // TODO: do something about unknown properties?
1201             break;
1202         }
1203 
1204         p = icalcomponent_get_next_property(vevent, ICAL_ANY_PROPERTY);
1205     }
1206 
1207     // according to rfc2445 the dtend shouldn't be written when it equals
1208     // start date. so assign one equal to start date.
1209     if (!dtEndProcessed && !event->hasDuration()) {
1210         event->setDtEnd(event->dtStart());
1211     }
1212 
1213     QString msade = event->nonKDECustomProperty("X-MICROSOFT-CDO-ALLDAYEVENT");
1214     if (!msade.isEmpty()) {
1215         bool allDay = (msade == QLatin1String("TRUE"));
1216         event->setAllDay(allDay);
1217     }
1218 
1219     if (mCompat) {
1220         mCompat->fixEmptySummary(event);
1221     }
1222 
1223     event->resetDirtyFields();
1224     return event;
1225 }
1226 
1227 FreeBusy::Ptr ICalFormatImpl::readFreeBusy(icalcomponent *vfreebusy)
1228 {
1229     FreeBusy::Ptr freebusy(new FreeBusy);
1230 
1231     readIncidenceBase(vfreebusy, freebusy);
1232 
1233     icalproperty *p = icalcomponent_get_first_property(vfreebusy, ICAL_ANY_PROPERTY);
1234 
1235     FreeBusyPeriod::List periods;
1236 
1237     while (p) {
1238         icalproperty_kind kind = icalproperty_isa(p);
1239         switch (kind) {
1240         case ICAL_DTSTART_PROPERTY: // start date and time (UTC)
1241             freebusy->setDtStart(readICalUtcDateTimeProperty(p, nullptr));
1242             break;
1243 
1244         case ICAL_DTEND_PROPERTY: // end Date and Time (UTC)
1245             freebusy->setDtEnd(readICalUtcDateTimeProperty(p, nullptr));
1246             break;
1247 
1248         case ICAL_FREEBUSY_PROPERTY: { // Any FreeBusy Times (UTC)
1249             icalperiodtype icalperiod = icalproperty_get_freebusy(p);
1250             QDateTime period_start = readICalUtcDateTime(p, icalperiod.start);
1251             FreeBusyPeriod period;
1252             if (!icaltime_is_null_time(icalperiod.end)) {
1253                 QDateTime period_end = readICalUtcDateTime(p, icalperiod.end);
1254                 period = FreeBusyPeriod(period_start, period_end);
1255             } else {
1256                 Duration duration(readICalDuration(icalperiod.duration));
1257                 period = FreeBusyPeriod(period_start, duration);
1258             }
1259 
1260             icalparameter *param = icalproperty_get_first_parameter(p, ICAL_FBTYPE_PARAMETER);
1261             if (param) {
1262                 icalparameter_fbtype fbType = icalparameter_get_fbtype(param);
1263                 switch (fbType) {
1264                 case ICAL_FBTYPE_FREE:
1265                     period.setType(FreeBusyPeriod::Free);
1266                     break;
1267                 case ICAL_FBTYPE_BUSY:
1268                     period.setType(FreeBusyPeriod::Busy);
1269                     break;
1270                 case ICAL_FBTYPE_BUSYTENTATIVE:
1271                     period.setType(FreeBusyPeriod::BusyTentative);
1272                     break;
1273                 case ICAL_FBTYPE_BUSYUNAVAILABLE:
1274                     period.setType(FreeBusyPeriod::BusyUnavailable);
1275                     break;
1276                 case ICAL_FBTYPE_X:
1277                     period.setType(FreeBusyPeriod::Unknown);
1278                     break;
1279                 case ICAL_FBTYPE_NONE:
1280                     period.setType(FreeBusyPeriod::Free);
1281                     break;
1282                 }
1283             }
1284 
1285             param = icalproperty_get_first_parameter(p, ICAL_X_PARAMETER);
1286             while (param) {
1287                 if (strncmp(icalparameter_get_xname(param), "X-SUMMARY", 9) == 0) {
1288                     period.setSummary(QString::fromUtf8(QByteArray::fromBase64(icalparameter_get_xvalue(param))));
1289                 }
1290                 if (strncmp(icalparameter_get_xname(param), "X-LOCATION", 10) == 0) {
1291                     period.setLocation(QString::fromUtf8(QByteArray::fromBase64(icalparameter_get_xvalue(param))));
1292                 }
1293                 param = icalproperty_get_next_parameter(p, ICAL_X_PARAMETER);
1294             }
1295 
1296             periods.append(period);
1297             break;
1298         }
1299 
1300         default:
1301             // TODO: do something about unknown properties?
1302             break;
1303         }
1304         p = icalcomponent_get_next_property(vfreebusy, ICAL_ANY_PROPERTY);
1305     }
1306     freebusy->addPeriods(periods);
1307 
1308     freebusy->resetDirtyFields();
1309     return freebusy;
1310 }
1311 
1312 Journal::Ptr ICalFormatImpl::readJournal(icalcomponent *vjournal, const ICalTimeZoneCache *tzList)
1313 {
1314     Journal::Ptr journal(new Journal);
1315     readIncidence(vjournal, journal, tzList);
1316 
1317     journal->resetDirtyFields();
1318     return journal;
1319 }
1320 
1321 Attendee ICalFormatImpl::readAttendee(icalproperty *attendee)
1322 {
1323     // the following is a hack to support broken calendars (like WebCalendar 1.0.x)
1324     // that include non-RFC-compliant attendees.  Otherwise libical 0.42 asserts.
1325     if (!icalproperty_get_value(attendee)) {
1326         return {};
1327     }
1328 
1329     icalparameter *p = nullptr;
1330 
1331     QString email = QString::fromUtf8(icalproperty_get_attendee(attendee));
1332     if (email.startsWith(QLatin1String("mailto:"), Qt::CaseInsensitive)) {
1333         email.remove(0, 7);
1334     }
1335 
1336     // libical may return everything after ATTENDEE tag if the rest is
1337     // not meaningful. Verify the address to filter out these cases.
1338     if (!Person::isValidEmail(email)) {
1339         return {};
1340     }
1341 
1342     QString name;
1343     QString uid;
1344     p = icalproperty_get_first_parameter(attendee, ICAL_CN_PARAMETER);
1345     if (p) {
1346         name = QString::fromUtf8(icalparameter_get_cn(p));
1347     } else {
1348     }
1349 
1350     bool rsvp = false;
1351     p = icalproperty_get_first_parameter(attendee, ICAL_RSVP_PARAMETER);
1352     if (p) {
1353         icalparameter_rsvp rsvpParameter = icalparameter_get_rsvp(p);
1354         if (rsvpParameter == ICAL_RSVP_TRUE) {
1355             rsvp = true;
1356         }
1357     }
1358 
1359     Attendee::PartStat status = Attendee::NeedsAction;
1360     p = icalproperty_get_first_parameter(attendee, ICAL_PARTSTAT_PARAMETER);
1361     if (p) {
1362         icalparameter_partstat partStatParameter = icalparameter_get_partstat(p);
1363         switch (partStatParameter) {
1364         default:
1365         case ICAL_PARTSTAT_NEEDSACTION:
1366             status = Attendee::NeedsAction;
1367             break;
1368         case ICAL_PARTSTAT_ACCEPTED:
1369             status = Attendee::Accepted;
1370             break;
1371         case ICAL_PARTSTAT_DECLINED:
1372             status = Attendee::Declined;
1373             break;
1374         case ICAL_PARTSTAT_TENTATIVE:
1375             status = Attendee::Tentative;
1376             break;
1377         case ICAL_PARTSTAT_DELEGATED:
1378             status = Attendee::Delegated;
1379             break;
1380         case ICAL_PARTSTAT_COMPLETED:
1381             status = Attendee::Completed;
1382             break;
1383         case ICAL_PARTSTAT_INPROCESS:
1384             status = Attendee::InProcess;
1385             break;
1386         }
1387     }
1388 
1389     Attendee::Role role = Attendee::ReqParticipant;
1390     p = icalproperty_get_first_parameter(attendee, ICAL_ROLE_PARAMETER);
1391     if (p) {
1392         icalparameter_role roleParameter = icalparameter_get_role(p);
1393         switch (roleParameter) {
1394         case ICAL_ROLE_CHAIR:
1395             role = Attendee::Chair;
1396             break;
1397         default:
1398         case ICAL_ROLE_REQPARTICIPANT:
1399             role = Attendee::ReqParticipant;
1400             break;
1401         case ICAL_ROLE_OPTPARTICIPANT:
1402             role = Attendee::OptParticipant;
1403             break;
1404         case ICAL_ROLE_NONPARTICIPANT:
1405             role = Attendee::NonParticipant;
1406             break;
1407         }
1408     }
1409 
1410     Attendee::CuType cuType = Attendee::Individual;
1411     p = icalproperty_get_first_parameter(attendee, ICAL_CUTYPE_PARAMETER);
1412     if (p) {
1413         icalparameter_cutype cutypeParameter = icalparameter_get_cutype(p);
1414         switch (cutypeParameter) {
1415         case ICAL_CUTYPE_X:
1416         case ICAL_CUTYPE_UNKNOWN:
1417             cuType = Attendee::Unknown;
1418             break;
1419         default:
1420         case ICAL_CUTYPE_NONE:
1421         case ICAL_CUTYPE_INDIVIDUAL:
1422             cuType = Attendee::Individual;
1423             break;
1424         case ICAL_CUTYPE_GROUP:
1425             cuType = Attendee::Group;
1426             break;
1427         case ICAL_CUTYPE_RESOURCE:
1428             cuType = Attendee::Resource;
1429             break;
1430         case ICAL_CUTYPE_ROOM:
1431             cuType = Attendee::Room;
1432             break;
1433         }
1434     }
1435 
1436     p = icalproperty_get_first_parameter(attendee, ICAL_X_PARAMETER);
1437     QMap<QByteArray, QString> custom;
1438     while (p) {
1439         QString xname = QString::fromLatin1(icalparameter_get_xname(p)).toUpper();
1440         QString xvalue = QString::fromUtf8(icalparameter_get_xvalue(p));
1441         if (xname == QLatin1String("X-UID")) {
1442             uid = xvalue;
1443         } else {
1444             custom[xname.toUtf8()] = xvalue;
1445         }
1446         p = icalproperty_get_next_parameter(attendee, ICAL_X_PARAMETER);
1447     }
1448 
1449     Attendee a(name, email, rsvp, status, role, uid);
1450     a.setCuType(cuType);
1451     a.customProperties().setCustomProperties(custom);
1452 
1453     p = icalproperty_get_first_parameter(attendee, ICAL_DELEGATEDTO_PARAMETER);
1454     if (p) {
1455         a.setDelegate(QLatin1String(icalparameter_get_delegatedto(p)));
1456     }
1457 
1458     p = icalproperty_get_first_parameter(attendee, ICAL_DELEGATEDFROM_PARAMETER);
1459     if (p) {
1460         a.setDelegator(QLatin1String(icalparameter_get_delegatedfrom(p)));
1461     }
1462 
1463     return a;
1464 }
1465 
1466 Person ICalFormatImpl::readOrganizer(icalproperty *organizer)
1467 {
1468     QString email = QString::fromUtf8(icalproperty_get_organizer(organizer));
1469     if (email.startsWith(QLatin1String("mailto:"), Qt::CaseInsensitive)) {
1470         email.remove(0, 7);
1471     }
1472     QString cn;
1473 
1474     icalparameter *p = icalproperty_get_first_parameter(organizer, ICAL_CN_PARAMETER);
1475 
1476     if (p) {
1477         cn = QString::fromUtf8(icalparameter_get_cn(p));
1478     }
1479     Person org(cn, email);
1480     // TODO: Treat sent-by, dir and language here, too
1481     return org;
1482 }
1483 
1484 Attachment ICalFormatImpl::readAttachment(icalproperty *attach)
1485 {
1486     Attachment attachment;
1487 
1488     QByteArray p;
1489     icalvalue *value = icalproperty_get_value(attach);
1490 
1491     switch (icalvalue_isa(value)) {
1492     case ICAL_ATTACH_VALUE: {
1493         icalattach *a = icalproperty_get_attach(attach);
1494         if (!icalattach_get_is_url(a)) {
1495             p = QByteArray(reinterpret_cast<const char *>(icalattach_get_data(a)));
1496             if (!p.isEmpty()) {
1497                 attachment = Attachment(p);
1498             }
1499         } else {
1500             p = icalattach_get_url(a);
1501             if (!p.isEmpty()) {
1502                 attachment = Attachment(QString::fromUtf8(p));
1503             }
1504         }
1505         break;
1506     }
1507     case ICAL_BINARY_VALUE: {
1508         icalattach *a = icalproperty_get_attach(attach);
1509         p = QByteArray(reinterpret_cast<const char *>(icalattach_get_data(a)));
1510         if (!p.isEmpty()) {
1511             attachment = Attachment(p);
1512         }
1513         break;
1514     }
1515     case ICAL_URI_VALUE:
1516         p = icalvalue_get_uri(value);
1517         attachment = Attachment(QString::fromUtf8(p));
1518         break;
1519     default:
1520         break;
1521     }
1522 
1523     if (!attachment.isEmpty()) {
1524         icalparameter *p = icalproperty_get_first_parameter(attach, ICAL_FMTTYPE_PARAMETER);
1525         if (p) {
1526             attachment.setMimeType(QLatin1String(icalparameter_get_fmttype(p)));
1527         }
1528 
1529         /* Support FILENAME property (Caldav). see https://datatracker.ietf.org/doc/html/rfc8607 */
1530         p = icalproperty_get_first_parameter(attach, ICAL_FILENAME_PARAMETER);
1531         if (p) {
1532             attachment.setLabel(QString::fromUtf8(icalparameter_get_xvalue(p)));
1533         }
1534 
1535         p = icalproperty_get_first_parameter(attach, ICAL_X_PARAMETER);
1536         while (p) {
1537             QString xname = QString::fromLatin1(icalparameter_get_xname(p)).toUpper();
1538             QString xvalue = QString::fromUtf8(icalparameter_get_xvalue(p));
1539             if (xname == QLatin1String("X-CONTENT-DISPOSITION")) {
1540                 attachment.setShowInline(xvalue.toLower() == QLatin1String("inline"));
1541             } else if (xname == QLatin1String("X-LABEL")) {
1542                 attachment.setLabel(xvalue);
1543             } else if (xname == QLatin1String("X-KONTACT-TYPE")) {
1544                 attachment.setLocal(xvalue.toLower() == QLatin1String("local"));
1545             }
1546             p = icalproperty_get_next_parameter(attach, ICAL_X_PARAMETER);
1547         }
1548 
1549         p = icalproperty_get_first_parameter(attach, ICAL_X_PARAMETER);
1550         while (p) {
1551             if (strncmp(icalparameter_get_xname(p), "X-LABEL", 7) == 0) {
1552                 attachment.setLabel(QString::fromUtf8(icalparameter_get_xvalue(p)));
1553             }
1554             p = icalproperty_get_next_parameter(attach, ICAL_X_PARAMETER);
1555         }
1556     }
1557 
1558     return attachment;
1559 }
1560 
1561 void ICalFormatImpl::readIncidence(icalcomponent *parent, const Incidence::Ptr &incidence, const ICalTimeZoneCache *tzlist)
1562 {
1563     readIncidenceBase(parent, incidence);
1564 
1565     icalproperty *p = icalcomponent_get_first_property(parent, ICAL_ANY_PROPERTY);
1566 
1567     const char *text;
1568     int intvalue;
1569     int inttext;
1570     icaldurationtype icalduration;
1571     QDateTime kdt;
1572     QDateTime dtstamp;
1573 
1574     QStringList categories;
1575 
1576     while (p) {
1577         icalproperty_kind kind = icalproperty_isa(p);
1578         switch (kind) {
1579         case ICAL_CREATED_PROPERTY:
1580             incidence->setCreated(readICalDateTimeProperty(p, tzlist));
1581             break;
1582 
1583         case ICAL_DTSTAMP_PROPERTY:
1584             dtstamp = readICalDateTimeProperty(p, tzlist);
1585             break;
1586 
1587         case ICAL_SEQUENCE_PROPERTY: // sequence
1588             intvalue = icalproperty_get_sequence(p);
1589             incidence->setRevision(intvalue);
1590             break;
1591 
1592         case ICAL_LASTMODIFIED_PROPERTY: // last modification UTC date/time
1593             incidence->setLastModified(readICalDateTimeProperty(p, tzlist));
1594             break;
1595 
1596         case ICAL_DTSTART_PROPERTY: { // start date and time
1597             bool allDay = false;
1598             kdt = readICalDateTimeProperty(p, tzlist, false, &allDay);
1599             incidence->setDtStart(kdt);
1600             incidence->setAllDay(allDay);
1601             break;
1602         }
1603 
1604         case ICAL_DURATION_PROPERTY: // start date and time
1605             icalduration = icalproperty_get_duration(p);
1606             incidence->setDuration(readICalDuration(icalduration));
1607             break;
1608 
1609         case ICAL_DESCRIPTION_PROPERTY: { // description
1610             QString textStr = QString::fromUtf8(icalproperty_get_description(p));
1611             if (!textStr.isEmpty()) {
1612                 QString valStr = QString::fromUtf8(icalproperty_get_parameter_as_string(p, "X-KDE-TEXTFORMAT"));
1613                 if (!valStr.compare(QLatin1String("HTML"), Qt::CaseInsensitive)) {
1614                     incidence->setDescription(textStr, true);
1615                 } else {
1616                     incidence->setDescription(textStr, false);
1617                 }
1618             }
1619         } break;
1620 
1621         case ICAL_SUMMARY_PROPERTY: { // summary
1622             QString textStr = QString::fromUtf8(icalproperty_get_summary(p));
1623             if (!textStr.isEmpty()) {
1624                 QString valStr = QString::fromUtf8(icalproperty_get_parameter_as_string(p, "X-KDE-TEXTFORMAT"));
1625                 if (!valStr.compare(QLatin1String("HTML"), Qt::CaseInsensitive)) {
1626                     incidence->setSummary(textStr, true);
1627                 } else {
1628                     incidence->setSummary(textStr, false);
1629                 }
1630             }
1631         } break;
1632 
1633         case ICAL_LOCATION_PROPERTY: { // location
1634             if (!icalproperty_get_value(p)) {
1635                 // Fix for #191472. This is a pre-crash guard in case libical was
1636                 // compiled in superstrict mode (--enable-icalerrors-are-fatal)
1637                 // TODO: pre-crash guard other property getters too.
1638                 break;
1639             }
1640             QString textStr = QString::fromUtf8(icalproperty_get_location(p));
1641             if (!textStr.isEmpty()) {
1642                 QString valStr = QString::fromUtf8(icalproperty_get_parameter_as_string(p, "X-KDE-TEXTFORMAT"));
1643                 if (!valStr.compare(QLatin1String("HTML"), Qt::CaseInsensitive)) {
1644                     incidence->setLocation(textStr, true);
1645                 } else {
1646                     incidence->setLocation(textStr, false);
1647                 }
1648             }
1649         } break;
1650 
1651         case ICAL_STATUS_PROPERTY: { // status
1652             Incidence::Status stat;
1653             switch (icalproperty_get_status(p)) {
1654             case ICAL_STATUS_TENTATIVE:
1655                 stat = Incidence::StatusTentative;
1656                 break;
1657             case ICAL_STATUS_CONFIRMED:
1658                 stat = Incidence::StatusConfirmed;
1659                 break;
1660             case ICAL_STATUS_COMPLETED:
1661                 stat = Incidence::StatusCompleted;
1662                 break;
1663             case ICAL_STATUS_NEEDSACTION:
1664                 stat = Incidence::StatusNeedsAction;
1665                 break;
1666             case ICAL_STATUS_CANCELLED:
1667                 stat = Incidence::StatusCanceled;
1668                 break;
1669             case ICAL_STATUS_INPROCESS:
1670                 stat = Incidence::StatusInProcess;
1671                 break;
1672             case ICAL_STATUS_DRAFT:
1673                 stat = Incidence::StatusDraft;
1674                 break;
1675             case ICAL_STATUS_FINAL:
1676                 stat = Incidence::StatusFinal;
1677                 break;
1678             case ICAL_STATUS_X:
1679                 incidence->setCustomStatus(QString::fromUtf8(icalvalue_get_x(icalproperty_get_value(p))));
1680                 stat = Incidence::StatusX;
1681                 break;
1682             case ICAL_STATUS_NONE:
1683             default:
1684                 stat = Incidence::StatusNone;
1685                 break;
1686             }
1687             if (stat != Incidence::StatusX) {
1688                 incidence->setStatus(stat);
1689             }
1690             break;
1691         }
1692 
1693         case ICAL_GEO_PROPERTY: { // geo
1694             icalgeotype geo = icalproperty_get_geo(p);
1695             incidence->setGeoLatitude(geo.lat);
1696             incidence->setGeoLongitude(geo.lon);
1697             break;
1698         }
1699 
1700         case ICAL_PRIORITY_PROPERTY: // priority
1701             intvalue = icalproperty_get_priority(p);
1702             if (mCompat) {
1703                 intvalue = mCompat->fixPriority(intvalue);
1704             }
1705             incidence->setPriority(intvalue);
1706             break;
1707 
1708         case ICAL_CATEGORIES_PROPERTY: { // categories
1709             // We have always supported multiple CATEGORIES properties per component
1710             // even though the RFC seems to indicate only 1 is permitted.
1711             // We can't change that -- in order to retain backwards compatibility.
1712             text = icalproperty_get_categories(p);
1713             const QString val = QString::fromUtf8(text);
1714             const QStringList lstVal = val.split(QLatin1Char(','), Qt::SkipEmptyParts);
1715             for (const QString &cat : lstVal) {
1716                 // ensure no duplicates
1717                 if (!categories.contains(cat)) {
1718                     categories.append(cat);
1719                 }
1720             }
1721             break;
1722         }
1723 
1724         case ICAL_RECURRENCEID_PROPERTY: // recurrenceId
1725             kdt = readICalDateTimeProperty(p, tzlist);
1726             if (kdt.isValid()) {
1727                 incidence->setRecurrenceId(kdt);
1728                 const icalparameter *param = icalproperty_get_first_parameter(p, ICAL_RANGE_PARAMETER);
1729                 if (param && icalparameter_get_range(param) == ICAL_RANGE_THISANDFUTURE) {
1730                     incidence->setThisAndFuture(true);
1731                 } else {
1732                     // A workaround for a bug in libical (https://github.com/libical/libical/issues/185)
1733                     // If a recurrenceId has both tzid and range, both parameters end up in the tzid.
1734                     // This results in invalid tzid's like: "Europe/Berlin;RANGE=THISANDFUTURE"
1735                     const icalparameter *param = icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER);
1736                     QString tzid = QString::fromLatin1(icalparameter_get_tzid(param));
1737                     const QStringList parts = tzid.toLower().split(QLatin1Char(';'));
1738                     if (parts.contains(QLatin1String("range=thisandfuture"))) {
1739                         incidence->setThisAndFuture(true);
1740                     }
1741                 }
1742             }
1743             break;
1744 
1745         case ICAL_RRULE_PROPERTY:
1746             readRecurrenceRule(p, incidence);
1747             break;
1748 
1749         case ICAL_RDATE_PROPERTY: {
1750             bool allDay = false;
1751             kdt = readICalDateTimeProperty(p, tzlist, false, &allDay);
1752             if (kdt.isValid()) {
1753                 if (allDay) {
1754                     incidence->recurrence()->addRDate(kdt.date());
1755                 } else {
1756                     incidence->recurrence()->addRDateTime(kdt);
1757                 }
1758             } else {
1759                 icaldatetimeperiodtype tp = icalproperty_get_rdate(p);
1760                 const QDateTime start = readICalDateTime(p, tp.period.start, tzlist, false);
1761                 if (icaltime_is_null_time(tp.period.end)) {
1762                     Period period(start, readICalDuration(tp.period.duration));
1763                     incidence->recurrence()->addRDateTimePeriod(period);
1764                 } else {
1765                     Period period(start, readICalDateTime(p, tp.period.end, tzlist, false));
1766                     incidence->recurrence()->addRDateTimePeriod(period);
1767                 }
1768             }
1769             break;
1770         }
1771 
1772         case ICAL_EXRULE_PROPERTY:
1773             readExceptionRule(p, incidence);
1774             break;
1775 
1776         case ICAL_EXDATE_PROPERTY: {
1777             bool allDay = false;
1778             kdt = readICalDateTimeProperty(p, tzlist, false, &allDay);
1779             if (allDay) {
1780                 incidence->recurrence()->addExDate(kdt.date());
1781             } else {
1782                 incidence->recurrence()->addExDateTime(kdt);
1783             }
1784             break;
1785         }
1786 
1787         case ICAL_CLASS_PROPERTY:
1788             inttext = icalproperty_get_class(p);
1789             if (inttext == ICAL_CLASS_PUBLIC) {
1790                 incidence->setSecrecy(Incidence::SecrecyPublic);
1791             } else if (inttext == ICAL_CLASS_CONFIDENTIAL) {
1792                 incidence->setSecrecy(Incidence::SecrecyConfidential);
1793             } else {
1794                 incidence->setSecrecy(Incidence::SecrecyPrivate);
1795             }
1796             break;
1797 
1798         case ICAL_ATTACH_PROPERTY: // attachments
1799             incidence->addAttachment(readAttachment(p));
1800             break;
1801 
1802         case ICAL_COLOR_PROPERTY:
1803             incidence->setColor(QString::fromUtf8(icalproperty_get_color(p)));
1804             break;
1805 
1806         default:
1807             // TODO: do something about unknown properties?
1808             break;
1809         }
1810 
1811         p = icalcomponent_get_next_property(parent, ICAL_ANY_PROPERTY);
1812     }
1813 
1814     // Set the scheduling ID
1815     const QString uid = incidence->customProperty("LIBKCAL", "ID");
1816     if (!uid.isNull()) {
1817         // The UID stored in incidencebase is actually the scheduling ID
1818         // It has to be stored in the iCal UID component for compatibility
1819         // with other iCal applications
1820         incidence->setSchedulingID(incidence->uid(), uid);
1821     }
1822 
1823     // Now that recurrence and exception stuff is completely set up,
1824     // do any backwards compatibility adjustments.
1825     if (incidence->recurs() && mCompat) {
1826         mCompat->fixRecurrence(incidence);
1827     }
1828 
1829     // add categories
1830     incidence->setCategories(categories);
1831 
1832     // iterate through all alarms
1833     for (icalcomponent *alarm = icalcomponent_get_first_component(parent, ICAL_VALARM_COMPONENT); alarm;
1834          alarm = icalcomponent_get_next_component(parent, ICAL_VALARM_COMPONENT)) {
1835         readAlarm(alarm, incidence);
1836     }
1837 
1838     // iterate through all conferences
1839     Conference::List conferences;
1840     for (auto *conf = icalcomponent_get_first_property(parent, ICAL_CONFERENCE_PROPERTY); conf;
1841          conf = icalcomponent_get_next_property(parent, ICAL_CONFERENCE_PROPERTY)) {
1842         conferences.push_back(readConference(conf));
1843     }
1844     incidence->setConferences(conferences);
1845 
1846     if (mCompat) {
1847         // Fix incorrect alarm settings by other applications (like outloook 9)
1848         mCompat->fixAlarms(incidence);
1849         mCompat->setCreatedToDtStamp(incidence, dtstamp);
1850     }
1851 }
1852 
1853 //@cond PRIVATE
1854 void ICalFormatImpl::readIncidenceBase(icalcomponent *parent, const IncidenceBase::Ptr &incidenceBase)
1855 {
1856     icalproperty *p = icalcomponent_get_first_property(parent, ICAL_ANY_PROPERTY);
1857     bool uidProcessed = false;
1858     while (p) {
1859         icalproperty_kind kind = icalproperty_isa(p);
1860         switch (kind) {
1861         case ICAL_UID_PROPERTY: // unique id
1862             uidProcessed = true;
1863             incidenceBase->setUid(QString::fromUtf8(icalproperty_get_uid(p)));
1864             break;
1865 
1866         case ICAL_ORGANIZER_PROPERTY: // organizer
1867             incidenceBase->setOrganizer(readOrganizer(p));
1868             break;
1869 
1870         case ICAL_ATTENDEE_PROPERTY: // attendee
1871             incidenceBase->addAttendee(readAttendee(p));
1872             break;
1873 
1874         case ICAL_COMMENT_PROPERTY:
1875             incidenceBase->addComment(QString::fromUtf8(icalproperty_get_comment(p)));
1876             break;
1877 
1878         case ICAL_CONTACT_PROPERTY:
1879             incidenceBase->addContact(QString::fromUtf8(icalproperty_get_contact(p)));
1880             break;
1881 
1882         case ICAL_URL_PROPERTY:
1883             incidenceBase->setUrl(QUrl(QString::fromUtf8(icalproperty_get_url(p))));
1884             break;
1885 
1886         default:
1887             break;
1888         }
1889 
1890         p = icalcomponent_get_next_property(parent, ICAL_ANY_PROPERTY);
1891     }
1892 
1893     if (!uidProcessed) {
1894         qCWarning(KCALCORE_LOG) << "The incidence didn't have any UID! Report a bug "
1895                                 << "to the application that generated this file.";
1896 
1897         // Our in-memory incidence has a random uid generated in Event's ctor.
1898         // Generate a deterministic UID from its properties.
1899         // Otherwise, next time we read the file, this function will return
1900         // an event with another random uid and we will have two events in the calendar.
1901         std::vector<const char *> properties(icalcomponent_count_properties(parent, ICAL_ANY_PROPERTY));
1902         icalproperty *p = icalcomponent_get_first_property(parent, ICAL_ANY_PROPERTY);
1903         for (const char *&str : properties) {
1904             str = icalproperty_as_ical_string(p);
1905             p = icalcomponent_get_next_property(parent, ICAL_ANY_PROPERTY);
1906         }
1907         std::sort(properties.begin(), properties.end(),
1908                 [](const char *str1, const char *str2) {
1909                     return strcmp(str1, str2) < 0;
1910                 });
1911         QCryptographicHash hasher(QCryptographicHash::Md5);
1912         for (const char *str : properties) {
1913             hasher.addData(str);
1914         }
1915         incidenceBase->setUid(QString::fromLatin1(hasher.result().toHex()));
1916     }
1917 
1918     // custom properties
1919     readCustomProperties(parent, incidenceBase.data());
1920 }
1921 
1922 void ICalFormatImpl::readCustomProperties(icalcomponent *parent, CustomProperties *properties)
1923 {
1924     QByteArray property;
1925     QString value;
1926     QString parameters;
1927     icalproperty *p = icalcomponent_get_first_property(parent, ICAL_X_PROPERTY);
1928     icalparameter *param = nullptr;
1929 
1930     while (p) {
1931         QString nvalue = QString::fromUtf8(icalproperty_get_x(p));
1932         if (nvalue.isEmpty()) {
1933             icalvalue *value = icalproperty_get_value(p);
1934             if (icalvalue_isa(value) == ICAL_TEXT_VALUE) {
1935                 // Calling icalvalue_get_text( value ) on a datetime value crashes.
1936                 nvalue = QString::fromUtf8(icalvalue_get_text(value));
1937             } else {
1938                 nvalue = QString::fromUtf8(icalproperty_get_value_as_string(p));
1939             }
1940         }
1941         const char *name = icalproperty_get_x_name(p);
1942         QByteArray nproperty(name);
1943         if (property != nproperty) {
1944             // New property
1945             if (!property.isEmpty()) {
1946                 properties->setNonKDECustomProperty(property, value, parameters);
1947             }
1948             property = name;
1949             value = nvalue;
1950             QStringList parametervalues;
1951             for (param = icalproperty_get_first_parameter(p, ICAL_ANY_PARAMETER); param; param = icalproperty_get_next_parameter(p, ICAL_ANY_PARAMETER)) {
1952                 // 'c' is owned by ical library => all we need to do is just use it
1953                 const char *c = icalparameter_as_ical_string(param);
1954                 parametervalues.push_back(QLatin1String(c));
1955             }
1956             parameters = parametervalues.join(QLatin1Char(';'));
1957         } else {
1958             value = value.append(QLatin1Char(',')).append(nvalue);
1959         }
1960         p = icalcomponent_get_next_property(parent, ICAL_X_PROPERTY);
1961     }
1962     if (!property.isEmpty()) {
1963         properties->setNonKDECustomProperty(property, value, parameters);
1964     }
1965 }
1966 //@endcond
1967 
1968 void ICalFormatImpl::readRecurrenceRule(icalproperty *rrule, const Incidence::Ptr &incidence)
1969 {
1970     Recurrence *recur = incidence->recurrence();
1971 
1972     struct icalrecurrencetype r = icalproperty_get_rrule(rrule);
1973     // dumpIcalRecurrence(r);
1974 
1975     RecurrenceRule *recurrule = new RecurrenceRule(/*incidence*/);
1976     recurrule->setStartDt(incidence->dtStart());
1977     readRecurrence(r, recurrule);
1978     recur->addRRule(recurrule);
1979 }
1980 
1981 void ICalFormatImpl::readExceptionRule(icalproperty *rrule, const Incidence::Ptr &incidence)
1982 {
1983     struct icalrecurrencetype r = icalproperty_get_exrule(rrule);
1984     // dumpIcalRecurrence(r);
1985 
1986     RecurrenceRule *recurrule = new RecurrenceRule(/*incidence*/);
1987     recurrule->setStartDt(incidence->dtStart());
1988     readRecurrence(r, recurrule);
1989 
1990     Recurrence *recur = incidence->recurrence();
1991     recur->addExRule(recurrule);
1992 }
1993 
1994 void ICalFormatImpl::readRecurrence(const struct icalrecurrencetype &r, RecurrenceRule *recur)
1995 {
1996     // Generate the RRULE string
1997     recur->setRRule(QLatin1String(icalrecurrencetype_as_string(const_cast<struct icalrecurrencetype *>(&r))));
1998     // Period
1999     switch (r.freq) {
2000     case ICAL_SECONDLY_RECURRENCE:
2001         recur->setRecurrenceType(RecurrenceRule::rSecondly);
2002         break;
2003     case ICAL_MINUTELY_RECURRENCE:
2004         recur->setRecurrenceType(RecurrenceRule::rMinutely);
2005         break;
2006     case ICAL_HOURLY_RECURRENCE:
2007         recur->setRecurrenceType(RecurrenceRule::rHourly);
2008         break;
2009     case ICAL_DAILY_RECURRENCE:
2010         recur->setRecurrenceType(RecurrenceRule::rDaily);
2011         break;
2012     case ICAL_WEEKLY_RECURRENCE:
2013         recur->setRecurrenceType(RecurrenceRule::rWeekly);
2014         break;
2015     case ICAL_MONTHLY_RECURRENCE:
2016         recur->setRecurrenceType(RecurrenceRule::rMonthly);
2017         break;
2018     case ICAL_YEARLY_RECURRENCE:
2019         recur->setRecurrenceType(RecurrenceRule::rYearly);
2020         break;
2021     case ICAL_NO_RECURRENCE:
2022     default:
2023         recur->setRecurrenceType(RecurrenceRule::rNone);
2024     }
2025     // Frequency
2026     recur->setFrequency(r.interval);
2027 
2028     // Duration & End Date
2029     if (!icaltime_is_null_time(r.until)) {
2030         icaltimetype t = r.until;
2031         recur->setEndDt(readICalUtcDateTime(nullptr, t));
2032     } else {
2033         if (r.count == 0) {
2034             recur->setDuration(-1);
2035         } else {
2036             recur->setDuration(r.count);
2037         }
2038     }
2039 
2040     // Week start setting
2041     short wkst = static_cast<short>((r.week_start + 5) % 7 + 1);
2042     recur->setWeekStart(wkst);
2043 
2044     // And now all BY*
2045     QList<int> lst;
2046     int i;
2047     int index = 0;
2048 
2049 // clang-format off
2050 //@cond PRIVATE
2051 #define readSetByList( rrulecomp, setfunc )                             \
2052     index = 0;                                                            \
2053     lst.clear();                                                          \
2054     while ( ( i = r.rrulecomp[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { \
2055         lst.append( i );                                                    \
2056     }                                                                     \
2057     if ( !lst.isEmpty() ) {                                               \
2058         recur->setfunc( lst );                                              \
2059     }
2060 //@endcond
2061     // clang-format on
2062 
2063     // BYSECOND, MINUTE and HOUR, MONTHDAY, YEARDAY, WEEKNUMBER, MONTH
2064     // and SETPOS are standard int lists, so we can treat them with the
2065     // same macro
2066     readSetByList(by_second, setBySeconds);
2067     readSetByList(by_minute, setByMinutes);
2068     readSetByList(by_hour, setByHours);
2069     readSetByList(by_month_day, setByMonthDays);
2070     readSetByList(by_year_day, setByYearDays);
2071     readSetByList(by_week_no, setByWeekNumbers);
2072     readSetByList(by_month, setByMonths);
2073     readSetByList(by_set_pos, setBySetPos);
2074 #undef readSetByList
2075 
2076     // BYDAY is a special case, since it's not an int list
2077     QList<RecurrenceRule::WDayPos> wdlst;
2078     short day;
2079     index = 0;
2080     while ((day = r.by_day[index++]) != ICAL_RECURRENCE_ARRAY_MAX) {
2081         RecurrenceRule::WDayPos pos;
2082         pos.setDay(static_cast<short>((icalrecurrencetype_day_day_of_week(day) + 5) % 7 + 1));
2083         pos.setPos(icalrecurrencetype_day_position(day));
2084         wdlst.append(pos);
2085     }
2086     if (!wdlst.isEmpty()) {
2087         recur->setByDays(wdlst);
2088     }
2089 
2090     // TODO: Store all X- fields of the RRULE inside the recurrence (so they are
2091     // preserved
2092 }
2093 
2094 void ICalFormatImpl::readAlarm(icalcomponent *alarm, const Incidence::Ptr &incidence)
2095 {
2096     Alarm::Ptr ialarm = incidence->newAlarm();
2097     ialarm->setRepeatCount(0);
2098     ialarm->setEnabled(true);
2099 
2100     // Determine the alarm's action type
2101     icalproperty *p = icalcomponent_get_first_property(alarm, ICAL_ACTION_PROPERTY);
2102     Alarm::Type type = Alarm::Display;
2103     icalproperty_action action = ICAL_ACTION_DISPLAY;
2104     if (!p) {
2105         qCDebug(KCALCORE_LOG) << "Unknown type of alarm, using default";
2106         // TODO: do something about unknown alarm type?
2107     } else {
2108         action = icalproperty_get_action(p);
2109         switch (action) {
2110         case ICAL_ACTION_DISPLAY:
2111             type = Alarm::Display;
2112             break;
2113         case ICAL_ACTION_AUDIO:
2114             type = Alarm::Audio;
2115             break;
2116         case ICAL_ACTION_PROCEDURE:
2117             type = Alarm::Procedure;
2118             break;
2119         case ICAL_ACTION_EMAIL:
2120             type = Alarm::Email;
2121             break;
2122         default:
2123             break;
2124             // TODO: do something about invalid alarm type?
2125         }
2126     }
2127     ialarm->setType(type);
2128 
2129     p = icalcomponent_get_first_property(alarm, ICAL_ANY_PROPERTY);
2130     while (p) {
2131         icalproperty_kind kind = icalproperty_isa(p);
2132 
2133         switch (kind) {
2134         case ICAL_TRIGGER_PROPERTY: {
2135             icaltriggertype trigger = icalproperty_get_trigger(p);
2136             if (!icaltime_is_null_time(trigger.time)) {
2137                 // set the trigger to a specific time (which is not in rfc2445, btw)
2138                 ialarm->setTime(readICalUtcDateTime(p, trigger.time));
2139             } else {
2140                 // set the trigger to an offset from the incidence start or end time.
2141                 if (!icaldurationtype_is_bad_duration(trigger.duration)) {
2142                     Duration duration(readICalDuration(trigger.duration));
2143                     icalparameter *param = icalproperty_get_first_parameter(p, ICAL_RELATED_PARAMETER);
2144                     if (param && icalparameter_get_related(param) == ICAL_RELATED_END) {
2145                         ialarm->setEndOffset(duration);
2146                     } else {
2147                         ialarm->setStartOffset(duration);
2148                     }
2149                 } else {
2150                     // a bad duration was encountered, just set a 0 duration from start
2151                     ialarm->setStartOffset(Duration(0));
2152                 }
2153             }
2154             break;
2155         }
2156         case ICAL_DURATION_PROPERTY: {
2157             icaldurationtype duration = icalproperty_get_duration(p);
2158             ialarm->setSnoozeTime(readICalDuration(duration));
2159             break;
2160         }
2161         case ICAL_REPEAT_PROPERTY:
2162             ialarm->setRepeatCount(icalproperty_get_repeat(p));
2163             break;
2164 
2165         case ICAL_DESCRIPTION_PROPERTY: {
2166             // Only in DISPLAY and EMAIL and PROCEDURE alarms
2167             QString description = QString::fromUtf8(icalproperty_get_description(p));
2168             switch (action) {
2169             case ICAL_ACTION_DISPLAY:
2170                 ialarm->setText(description);
2171                 break;
2172             case ICAL_ACTION_PROCEDURE:
2173                 ialarm->setProgramArguments(description);
2174                 break;
2175             case ICAL_ACTION_EMAIL:
2176                 ialarm->setMailText(description);
2177                 break;
2178             default:
2179                 break;
2180             }
2181             break;
2182         }
2183         case ICAL_SUMMARY_PROPERTY:
2184             // Only in EMAIL alarm
2185             ialarm->setMailSubject(QString::fromUtf8(icalproperty_get_summary(p)));
2186             break;
2187 
2188         case ICAL_ATTENDEE_PROPERTY: {
2189             // Only in EMAIL alarm
2190             QString email = QString::fromUtf8(icalproperty_get_attendee(p));
2191             if (email.startsWith(QLatin1String("mailto:"), Qt::CaseInsensitive)) {
2192                 email.remove(0, 7);
2193             }
2194             QString name;
2195             icalparameter *param = icalproperty_get_first_parameter(p, ICAL_CN_PARAMETER);
2196             if (param) {
2197                 name = QString::fromUtf8(icalparameter_get_cn(param));
2198             }
2199             ialarm->addMailAddress(Person(name, email));
2200             break;
2201         }
2202 
2203         case ICAL_ATTACH_PROPERTY: {
2204             // Only in AUDIO and EMAIL and PROCEDURE alarms
2205             Attachment attach = readAttachment(p);
2206             if (!attach.isEmpty() && attach.isUri()) {
2207                 switch (action) {
2208                 case ICAL_ACTION_AUDIO:
2209                     ialarm->setAudioFile(attach.uri());
2210                     break;
2211                 case ICAL_ACTION_PROCEDURE:
2212                     ialarm->setProgramFile(attach.uri());
2213                     break;
2214                 case ICAL_ACTION_EMAIL:
2215                     ialarm->addMailAttachment(attach.uri());
2216                     break;
2217                 default:
2218                     break;
2219                 }
2220             } else {
2221                 qCDebug(KCALCORE_LOG) << "Alarm attachments currently only support URIs,"
2222                                       << "but no binary data";
2223             }
2224             break;
2225         }
2226         default:
2227             break;
2228         }
2229         p = icalcomponent_get_next_property(alarm, ICAL_ANY_PROPERTY);
2230     }
2231 
2232     // custom properties
2233     readCustomProperties(alarm, ialarm.data());
2234 
2235     QString locationRadius = ialarm->nonKDECustomProperty("X-LOCATION-RADIUS");
2236     if (!locationRadius.isEmpty()) {
2237         ialarm->setLocationRadius(locationRadius.toInt());
2238         ialarm->setHasLocationRadius(true);
2239     }
2240 
2241     if (ialarm->customProperty(APP_NAME_FOR_XPROPERTIES, ENABLED_ALARM_XPROPERTY) == QLatin1String("FALSE")) {
2242         ialarm->setEnabled(false);
2243     }
2244     // TODO: check for consistency of alarm properties
2245 }
2246 
2247 icaldatetimeperiodtype ICalFormatImpl::writeICalDatePeriod(const QDate &date)
2248 {
2249     icaldatetimeperiodtype t;
2250     t.time = writeICalDate(date);
2251     t.period = icalperiodtype_null_period();
2252     return t;
2253 }
2254 
2255 Conference ICalFormatImpl::readConference(icalproperty *prop)
2256 {
2257     Conference conf;
2258     conf.setUri(QUrl(QString::fromUtf8(icalproperty_get_conference(prop))));
2259     conf.setLabel(QString::fromUtf8(icalproperty_get_parameter_as_string(prop, "LABEL")));
2260     conf.setFeatures(QString::fromUtf8(icalproperty_get_parameter_as_string(prop, "FEATURE")).split(QLatin1Char(',')));
2261     conf.setLanguage(QString::fromUtf8(icalproperty_get_parameter_as_string(prop, "LANGUAGE")));
2262     return conf;
2263 }
2264 
2265 icaltimetype ICalFormatImpl::writeICalDate(const QDate &date)
2266 {
2267     icaltimetype t = icaltime_null_time();
2268 
2269     t.year = date.year();
2270     t.month = date.month();
2271     t.day = date.day();
2272 
2273     t.hour = 0;
2274     t.minute = 0;
2275     t.second = 0;
2276 
2277     t.is_date = 1;
2278     t.zone = nullptr;
2279 
2280     return t;
2281 }
2282 
2283 static bool dateTimeIsInUTC(const QDateTime &datetime)
2284 {
2285     return datetime.timeSpec() == Qt::UTC || (datetime.timeSpec() == Qt::TimeZone && datetime.timeZone() == QTimeZone::utc())
2286         || (datetime.timeSpec() == Qt::OffsetFromUTC && datetime.offsetFromUtc() == 0);
2287 }
2288 
2289 icaltimetype ICalFormatImpl::writeICalDateTime(const QDateTime &datetime, bool dateOnly)
2290 {
2291     icaltimetype t = icaltime_null_time();
2292 
2293     t.year = datetime.date().year();
2294     t.month = datetime.date().month();
2295     t.day = datetime.date().day();
2296 
2297     t.is_date = dateOnly;
2298 
2299     if (!t.is_date) {
2300         t.hour = datetime.time().hour();
2301         t.minute = datetime.time().minute();
2302         t.second = datetime.time().second();
2303     }
2304     t.zone = nullptr; // zone is NOT set
2305     if (dateTimeIsInUTC(datetime)) {
2306         t = icaltime_convert_to_zone(t, icaltimezone_get_utc_timezone());
2307     }
2308     return t;
2309 }
2310 
2311 icalproperty *ICalFormatImpl::writeICalDateTimeProperty(const icalproperty_kind type, const QDateTime &dt, TimeZoneList *tzUsedList)
2312 {
2313     icaltimetype t;
2314 
2315     switch (type) {
2316     case ICAL_DTSTAMP_PROPERTY:
2317     case ICAL_CREATED_PROPERTY:
2318     case ICAL_LASTMODIFIED_PROPERTY:
2319         t = writeICalDateTime(dt.toUTC());
2320         break;
2321     default:
2322         t = writeICalDateTime(dt);
2323         break;
2324     }
2325 
2326     icalproperty *p;
2327     switch (type) {
2328     case ICAL_DTSTAMP_PROPERTY:
2329         p = icalproperty_new_dtstamp(t);
2330         break;
2331     case ICAL_CREATED_PROPERTY:
2332         p = icalproperty_new_created(t);
2333         break;
2334     case ICAL_LASTMODIFIED_PROPERTY:
2335         p = icalproperty_new_lastmodified(t);
2336         break;
2337     case ICAL_DTSTART_PROPERTY: // start date and time
2338         p = icalproperty_new_dtstart(t);
2339         break;
2340     case ICAL_DTEND_PROPERTY: // end date and time
2341         p = icalproperty_new_dtend(t);
2342         break;
2343     case ICAL_DUE_PROPERTY:
2344         p = icalproperty_new_due(t);
2345         break;
2346     case ICAL_RECURRENCEID_PROPERTY:
2347         p = icalproperty_new_recurrenceid(t);
2348         break;
2349     case ICAL_EXDATE_PROPERTY:
2350         p = icalproperty_new_exdate(t);
2351         break;
2352     case ICAL_X_PROPERTY: {
2353         p = icalproperty_new_x("");
2354         icaltimetype timeType = writeICalDateTime(dt);
2355         icalvalue *text = icalvalue_new_datetime(timeType);
2356         icalproperty_set_value(p, text);
2357     } break;
2358     default: {
2359         icaldatetimeperiodtype tp;
2360         tp.time = t;
2361         tp.period = icalperiodtype_null_period();
2362         switch (type) {
2363         case ICAL_RDATE_PROPERTY:
2364             p = icalproperty_new_rdate(tp);
2365             break;
2366         default:
2367             return nullptr;
2368         }
2369     }
2370     }
2371 
2372     QTimeZone qtz;
2373     if (!icaltime_is_utc(t) && !dateTimeIsInUTC(dt) && dt.timeSpec() != Qt::LocalTime) {
2374         qtz = dt.timeZone();
2375     }
2376 
2377     if (qtz.isValid()) {
2378         if (tzUsedList) {
2379             if (!tzUsedList->contains(qtz)) {
2380                 tzUsedList->push_back(qtz);
2381             }
2382         }
2383 
2384         icalproperty_add_parameter(p, icalparameter_new_tzid(qtz.id().constData()));
2385     }
2386     return p;
2387 }
2388 
2389 QDateTime ICalFormatImpl::readICalDateTime(icalproperty *p, const icaltimetype &t, const ICalTimeZoneCache *tzCache, bool utc)
2390 {
2391     //  qCDebug(KCALCORE_LOG);
2392     //  _dumpIcaltime( t );
2393 
2394     QTimeZone timeZone;
2395     if (icaltime_is_utc(t) || t.zone == icaltimezone_get_utc_timezone()) {
2396         timeZone = QTimeZone::utc(); // the time zone is UTC
2397         utc = false; // no need to convert to UTC
2398     } else {
2399         icalparameter *param = p ? icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) : nullptr;
2400         QByteArray tzid = param ? QByteArray(icalparameter_get_tzid(param)) : QByteArray();
2401 
2402         // A workaround for a bug in libical (https://github.com/libical/libical/issues/185)
2403         // If a recurrenceId has both tzid and range, both parameters end up in the tzid.
2404         // This results in invalid tzid's like: "Europe/Berlin;RANGE=THISANDFUTURE"
2405         QStringList parts = QString::fromLatin1(tzid).split(QLatin1Char(';'));
2406         if (parts.count() > 1) {
2407             tzid = parts.first().toLatin1();
2408         }
2409 
2410         if (tzCache) {
2411             // First try to get the timezone from cache
2412             timeZone = tzCache->tzForTime(QDateTime({t.year, t.month, t.day}, {}), tzid);
2413         }
2414         if (!timeZone.isValid() && !tzid.isEmpty()) {
2415             // Fallback to trying to match against Qt timezone
2416             timeZone = QTimeZone(tzid);
2417         }
2418         // If Time zone is still invalid, we will use LocalTime as TimeSpec.
2419     }
2420     QTime resultTime;
2421     if (!t.is_date) {
2422         resultTime = QTime(t.hour, t.minute, t.second);
2423     }
2424     QDateTime result;
2425     if (timeZone.isValid()) {
2426         result = QDateTime(QDate(t.year, t.month, t.day), resultTime, timeZone);
2427     } else {
2428         result = QDateTime(QDate(t.year, t.month, t.day), resultTime);
2429     }
2430     return utc ? result.toUTC() : result;
2431 }
2432 
2433 QDate ICalFormatImpl::readICalDate(const icaltimetype &t)
2434 {
2435     return QDate(t.year, t.month, t.day);
2436 }
2437 
2438 QDateTime ICalFormatImpl::readICalDateTimeProperty(icalproperty *p, const ICalTimeZoneCache *tzList, bool utc, bool *allDay)
2439 {
2440     icaldatetimeperiodtype tp;
2441     icalproperty_kind kind = icalproperty_isa(p);
2442     switch (kind) {
2443     case ICAL_CREATED_PROPERTY: // UTC date/time
2444         tp.time = icalproperty_get_created(p);
2445         utc = true;
2446         break;
2447     case ICAL_DTSTAMP_PROPERTY: // UTC date/time
2448         tp.time = icalproperty_get_dtstamp(p);
2449         utc = true;
2450         break;
2451     case ICAL_LASTMODIFIED_PROPERTY: // last modification UTC date/time
2452         tp.time = icalproperty_get_lastmodified(p);
2453         utc = true;
2454         break;
2455     case ICAL_DTSTART_PROPERTY: // start date and time (UTC for freebusy)
2456         tp.time = icalproperty_get_dtstart(p);
2457         break;
2458     case ICAL_DTEND_PROPERTY: // end date and time (UTC for freebusy)
2459         tp.time = icalproperty_get_dtend(p);
2460         break;
2461     case ICAL_DUE_PROPERTY: // due date/time
2462         tp.time = icalproperty_get_due(p);
2463         break;
2464     case ICAL_COMPLETED_PROPERTY: // UTC completion date/time
2465         tp.time = icalproperty_get_completed(p);
2466         utc = true;
2467         break;
2468     case ICAL_RECURRENCEID_PROPERTY:
2469         tp.time = icalproperty_get_recurrenceid(p);
2470         break;
2471     case ICAL_EXDATE_PROPERTY:
2472         tp.time = icalproperty_get_exdate(p);
2473         break;
2474     case ICAL_X_PROPERTY: {
2475         const char *name = icalproperty_get_x_name(p);
2476         if (QLatin1String(name) == QLatin1String("X-KDE-LIBKCAL-DTRECURRENCE")) {
2477             const char *value = icalvalue_as_ical_string(icalproperty_get_value(p));
2478             icalvalue *v = icalvalue_new_from_string(ICAL_DATETIME_VALUE, value);
2479             tp.time = icalvalue_get_datetime(v);
2480             icalvalue_free(v);
2481             break;
2482         }
2483     } // end of ICAL_X_PROPERTY
2484         Q_FALLTHROUGH();
2485     default:
2486         switch (kind) {
2487         case ICAL_RDATE_PROPERTY:
2488             tp = icalproperty_get_rdate(p);
2489             break;
2490         default:
2491             return QDateTime();
2492         }
2493         if (!icaltime_is_valid_time(tp.time)) {
2494             return QDateTime(); // a time period was found (not implemented yet)
2495         }
2496         break;
2497     }
2498 
2499     if (allDay) {
2500         *allDay = tp.time.is_date;
2501     }
2502 
2503     if (tp.time.is_date) {
2504         return QDateTime(readICalDate(tp.time), QTime());
2505     } else {
2506         return readICalDateTime(p, tp.time, tzList, utc);
2507     }
2508 }
2509 
2510 icaldurationtype ICalFormatImpl::writeICalDuration(const Duration &duration)
2511 {
2512     // should be able to use icaldurationtype_from_int(), except we know
2513     // that some older tools do not properly support weeks. So we never
2514     // set a week duration, only days
2515 
2516     icaldurationtype d;
2517 
2518     int value = duration.value();
2519     d.is_neg = (value < 0) ? 1 : 0;
2520     if (value < 0) {
2521         value = -value;
2522     }
2523     // RFC2445 states that an ical duration value must be
2524     // EITHER weeks OR days/time, not both.
2525     if (duration.isDaily()) {
2526         if (!(value % 7)) {
2527             d.weeks = value / 7;
2528             d.days = 0;
2529         } else {
2530             d.weeks = 0;
2531             d.days = value;
2532         }
2533         d.hours = d.minutes = d.seconds = 0;
2534     } else {
2535         if (!(value % gSecondsPerWeek)) {
2536             d.weeks = value / gSecondsPerWeek;
2537             d.days = d.hours = d.minutes = d.seconds = 0;
2538         } else {
2539             d.weeks = 0;
2540             d.days = value / gSecondsPerDay;
2541             value %= gSecondsPerDay;
2542             d.hours = value / gSecondsPerHour;
2543             value %= gSecondsPerHour;
2544             d.minutes = value / gSecondsPerMinute;
2545             value %= gSecondsPerMinute;
2546             d.seconds = value;
2547         }
2548     }
2549 
2550     return d;
2551 }
2552 
2553 Duration ICalFormatImpl::readICalDuration(const icaldurationtype &d)
2554 {
2555     int days = d.weeks * 7;
2556     days += d.days;
2557     int seconds = d.hours * gSecondsPerHour;
2558     seconds += d.minutes * gSecondsPerMinute;
2559     seconds += d.seconds;
2560     if (seconds || !days) { // Create second-type duration for 0 delay durations.
2561         seconds += days * gSecondsPerDay;
2562         if (d.is_neg) {
2563             seconds = -seconds;
2564         }
2565         return Duration(seconds, Duration::Seconds);
2566     } else {
2567         if (d.is_neg) {
2568             days = -days;
2569         }
2570         return Duration(days, Duration::Days);
2571     }
2572 }
2573 
2574 icalcomponent *ICalFormatImpl::createCalendarComponent(const Calendar::Ptr &cal)
2575 {
2576     icalcomponent *calendar;
2577 
2578     // Root component
2579     calendar = icalcomponent_new(ICAL_VCALENDAR_COMPONENT);
2580 
2581     // Product Identifier
2582     icalproperty *p = icalproperty_new_prodid(CalFormat::productId().toUtf8().constData());
2583     icalcomponent_add_property(calendar, p);
2584 
2585     // iCalendar version (2.0)
2586     p = icalproperty_new_version(const_cast<char *>(_ICAL_VERSION));
2587     icalcomponent_add_property(calendar, p);
2588 
2589     // Implementation Version
2590     p = icalproperty_new_x(_ICAL_IMPLEMENTATION_VERSION);
2591     icalproperty_set_x_name(p, IMPLEMENTATION_VERSION_XPROPERTY);
2592     icalcomponent_add_property(calendar, p);
2593 
2594     // Add time zone
2595     // NOTE: Commented out since relevant timezones are added by the caller.
2596     // Previously we got some timezones listed twice in the ical file.
2597     /*
2598     if ( cal && cal->timeZones() ) {
2599       const ICalTimeZones::ZoneMap zmaps = cal->timeZones()->zones();
2600       for ( ICalTimeZones::ZoneMap::ConstIterator it=zmaps.constBegin();
2601             it != zmaps.constEnd(); ++it ) {
2602         icaltimezone *icaltz = (*it).icalTimezone();
2603         if ( !icaltz ) {
2604           qCritical() << "bad time zone";
2605         } else {
2606           icalcomponent *tz = icalcomponent_new_clone( icaltimezone_get_component( icaltz ) );
2607           icalcomponent_add_component( calendar, tz );
2608           icaltimezone_free( icaltz, 1 );
2609         }
2610       }
2611     }
2612     */
2613     // Custom properties
2614     if (cal != nullptr) {
2615         writeCustomProperties(calendar, cal.data());
2616     }
2617 
2618     return calendar;
2619 }
2620 
2621 Incidence::Ptr ICalFormatImpl::readOneIncidence(icalcomponent *calendar, const ICalTimeZoneCache *tzlist)
2622 {
2623     if (!calendar) {
2624         qCWarning(KCALCORE_LOG) << "Populate called with empty calendar";
2625         return Incidence::Ptr();
2626     }
2627     icalcomponent *c = icalcomponent_get_first_component(calendar, ICAL_VEVENT_COMPONENT);
2628     if (c) {
2629         return readEvent(c, tzlist);
2630     }
2631     c = icalcomponent_get_first_component(calendar, ICAL_VTODO_COMPONENT);
2632     if (c) {
2633         return readTodo(c, tzlist);
2634     }
2635     c = icalcomponent_get_first_component(calendar, ICAL_VJOURNAL_COMPONENT);
2636     if (c) {
2637         return readJournal(c, tzlist);
2638     }
2639     qCWarning(KCALCORE_LOG) << "Found no incidence";
2640     return Incidence::Ptr();
2641 }
2642 
2643 // take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc.
2644 // and break it down from its tree-like format into the dictionary format
2645 // that is used internally in the ICalFormatImpl.
2646 bool ICalFormatImpl::populate(const Calendar::Ptr &cal, icalcomponent *calendar)
2647 {
2648     // qCDebug(KCALCORE_LOG)<<"Populate called";
2649 
2650     // this function will populate the caldict dictionary and other event
2651     // lists. It turns vevents into Events and then inserts them.
2652 
2653     if (!calendar) {
2654         qCWarning(KCALCORE_LOG) << "Populate called with empty calendar";
2655         return false;
2656     }
2657 
2658     // TODO: check for METHOD
2659 
2660     icalproperty *p = icalcomponent_get_first_property(calendar, ICAL_X_PROPERTY);
2661     QString implementationVersion;
2662 
2663     while (p) {
2664         const char *name = icalproperty_get_x_name(p);
2665         QByteArray nproperty(name);
2666         if (nproperty == QByteArray(IMPLEMENTATION_VERSION_XPROPERTY)) {
2667             QString nvalue = QString::fromUtf8(icalproperty_get_x(p));
2668             if (nvalue.isEmpty()) {
2669                 icalvalue *value = icalproperty_get_value(p);
2670                 if (icalvalue_isa(value) == ICAL_TEXT_VALUE) {
2671                     nvalue = QString::fromUtf8(icalvalue_get_text(value));
2672                 }
2673             }
2674             implementationVersion = nvalue;
2675             icalcomponent_remove_property(calendar, p);
2676             icalproperty_free(p);
2677         }
2678         p = icalcomponent_get_next_property(calendar, ICAL_X_PROPERTY);
2679     }
2680 
2681     p = icalcomponent_get_first_property(calendar, ICAL_PRODID_PROPERTY);
2682     if (!p) {
2683         qCDebug(KCALCORE_LOG) << "No PRODID property found";
2684         mLoadedProductId.clear();
2685     } else {
2686         mLoadedProductId = QString::fromUtf8(icalproperty_get_prodid(p));
2687 
2688         mCompat.reset(CompatFactory::createCompat(mLoadedProductId, implementationVersion));
2689     }
2690 
2691     p = icalcomponent_get_first_property(calendar, ICAL_VERSION_PROPERTY);
2692     if (!p) {
2693         qCDebug(KCALCORE_LOG) << "No VERSION property found";
2694         mParent->setException(new Exception(Exception::CalVersionUnknown));
2695         return false;
2696     } else {
2697         const char *version = icalproperty_get_version(p);
2698         if (!version) {
2699             qCDebug(KCALCORE_LOG) << "No VERSION property found";
2700             mParent->setException(new Exception(Exception::VersionPropertyMissing));
2701 
2702             return false;
2703         }
2704         if (strcmp(version, "1.0") == 0) {
2705             qCDebug(KCALCORE_LOG) << "Expected iCalendar, got vCalendar";
2706             mParent->setException(new Exception(Exception::CalVersion1));
2707             return false;
2708         } else if (strcmp(version, "2.0") != 0) {
2709             qCDebug(KCALCORE_LOG) << "Expected iCalendar, got unknown format";
2710             mParent->setException(new Exception(Exception::CalVersionUnknown));
2711             return false;
2712         }
2713     }
2714 
2715     // Populate the calendar's time zone collection with all VTIMEZONE components
2716     ICalTimeZoneCache timeZoneCache;
2717     ICalTimeZoneParser parser(&timeZoneCache);
2718     parser.parse(calendar);
2719 
2720     // custom properties
2721     readCustomProperties(calendar, cal.data());
2722 
2723     // Store all events with a relatedTo property in a list for post-processing
2724     mEventsRelate.clear();
2725     mTodosRelate.clear();
2726     // TODO: make sure that only actually added events go to this lists.
2727 
2728     icalcomponent *c = icalcomponent_get_first_component(calendar, ICAL_VTODO_COMPONENT);
2729     while (c) {
2730         Todo::Ptr todo = readTodo(c, &timeZoneCache);
2731         if (todo) {
2732             // qCDebug(KCALCORE_LOG) << "todo is not zero";;
2733             Todo::Ptr old = cal->todo(todo->uid(), todo->recurrenceId());
2734             if (old) {
2735                 if (old->uid().isEmpty()) {
2736                     qCWarning(KCALCORE_LOG) << "Skipping invalid VTODO";
2737                     c = icalcomponent_get_next_component(calendar, ICAL_VTODO_COMPONENT);
2738                     continue;
2739                 }
2740                 // qCDebug(KCALCORE_LOG) << "Found an old todo with uid " << old->uid();
2741                 if (todo->revision() > old->revision()) {
2742                     // qCDebug(KCALCORE_LOG) << "Replacing old todo " << old.data() << " with this one " << todo.data();
2743                     cal->deleteTodo(old); // move old to deleted
2744                     removeAllICal(mTodosRelate, old);
2745                     cal->addTodo(todo); // and replace it with this one
2746                 }
2747             } else {
2748                 // qCDebug(KCALCORE_LOG) << "Adding todo " << todo.data() << todo->uid();
2749                 cal->addTodo(todo); // just add this one
2750             }
2751         }
2752         c = icalcomponent_get_next_component(calendar, ICAL_VTODO_COMPONENT);
2753     }
2754 
2755     // Iterate through all events
2756     c = icalcomponent_get_first_component(calendar, ICAL_VEVENT_COMPONENT);
2757     while (c) {
2758         Event::Ptr event = readEvent(c, &timeZoneCache);
2759         if (event) {
2760             // qCDebug(KCALCORE_LOG) << "event is not zero";
2761             Event::Ptr old = cal->event(event->uid(), event->recurrenceId());
2762             if (old) {
2763                 if (old->uid().isEmpty()) {
2764                     qCWarning(KCALCORE_LOG) << "Skipping invalid VEVENT";
2765                     c = icalcomponent_get_next_component(calendar, ICAL_VEVENT_COMPONENT);
2766                     continue;
2767                 }
2768                 // qCDebug(KCALCORE_LOG) << "Found an old event with uid " << old->uid();
2769                 if (event->revision() > old->revision()) {
2770                     // qCDebug(KCALCORE_LOG) << "Replacing old event " << old.data()
2771                     //                       << " with this one " << event.data();
2772                     cal->deleteEvent(old); // move old to deleted
2773                     removeAllICal(mEventsRelate, old);
2774                     cal->addEvent(event); // and replace it with this one
2775                 }
2776             } else {
2777                 // qCDebug(KCALCORE_LOG) << "Adding event " << event.data() << event->uid();
2778                 cal->addEvent(event); // just add this one
2779             }
2780         }
2781         c = icalcomponent_get_next_component(calendar, ICAL_VEVENT_COMPONENT);
2782     }
2783 
2784     // Iterate through all journals
2785     c = icalcomponent_get_first_component(calendar, ICAL_VJOURNAL_COMPONENT);
2786     while (c) {
2787         Journal::Ptr journal = readJournal(c, &timeZoneCache);
2788         if (journal) {
2789             Journal::Ptr old = cal->journal(journal->uid(), journal->recurrenceId());
2790             if (old) {
2791                 if (journal->revision() > old->revision()) {
2792                     cal->deleteJournal(old); // move old to deleted
2793                     cal->addJournal(journal); // and replace it with this one
2794                 }
2795             } else {
2796                 cal->addJournal(journal); // just add this one
2797             }
2798         }
2799         c = icalcomponent_get_next_component(calendar, ICAL_VJOURNAL_COMPONENT);
2800     }
2801 
2802     // TODO: Remove any previous time zones no longer referenced in the calendar
2803 
2804     return true;
2805 }
2806 
2807 QString ICalFormatImpl::extractErrorProperty(icalcomponent *c)
2808 {
2809     QString errorMessage;
2810 
2811     icalproperty *error = icalcomponent_get_first_property(c, ICAL_XLICERROR_PROPERTY);
2812     while (error) {
2813         errorMessage += QLatin1String(icalproperty_get_xlicerror(error));
2814         errorMessage += QLatin1Char('\n');
2815         error = icalcomponent_get_next_property(c, ICAL_XLICERROR_PROPERTY);
2816     }
2817 
2818     return errorMessage;
2819 }
2820 
2821 /*
2822 void ICalFormatImpl::dumpIcalRecurrence( const icalrecurrencetype &r )
2823 {
2824   int i;
2825 
2826   qCDebug(KCALCORE_LOG) << " Freq:" << int( r.freq );
2827   qCDebug(KCALCORE_LOG) << " Until:" << icaltime_as_ical_string( r.until );
2828   qCDebug(KCALCORE_LOG) << " Count:" << r.count;
2829   if ( r.by_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
2830     int index = 0;
2831     QString out = " By Day: ";
2832     while ( ( i = r.by_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
2833       out.append( QString::number( i ) + ' ' );
2834     }
2835     qCDebug(KCALCORE_LOG) << out;
2836   }
2837   if ( r.by_month_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
2838     int index = 0;
2839     QString out = " By Month Day: ";
2840     while ( ( i = r.by_month_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
2841       out.append( QString::number( i ) + ' ' );
2842     }
2843     qCDebug(KCALCORE_LOG) << out;
2844   }
2845   if ( r.by_year_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
2846     int index = 0;
2847     QString out = " By Year Day: ";
2848     while ( ( i = r.by_year_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
2849       out.append( QString::number( i ) + ' ' );
2850     }
2851     qCDebug(KCALCORE_LOG) << out;
2852   }
2853   if ( r.by_month[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
2854     int index = 0;
2855     QString out = " By Month: ";
2856     while ( ( i = r.by_month[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
2857       out.append( QString::number( i ) + ' ' );
2858     }
2859     qCDebug(KCALCORE_LOG) << out;
2860   }
2861   if ( r.by_set_pos[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
2862     int index = 0;
2863     QString out = " By Set Pos: ";
2864     while ( ( i = r.by_set_pos[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
2865       qCDebug(KCALCORE_LOG) << "=========" << i;
2866       out.append( QString::number( i ) + ' ' );
2867     }
2868     qCDebug(KCALCORE_LOG) << out;
2869   }
2870 }
2871 */
2872 
2873 icalcomponent *ICalFormatImpl::createScheduleComponent(const IncidenceBase::Ptr &incidence, iTIPMethod method)
2874 {
2875     icalcomponent *message = createCalendarComponent();
2876 
2877     // Create VTIMEZONE components for this incidence
2878     TimeZoneList zones;
2879     if (incidence) {
2880         const QDateTime kd1 = incidence->dateTime(IncidenceBase::RoleStartTimeZone);
2881         const QDateTime kd2 = incidence->dateTime(IncidenceBase::RoleEndTimeZone);
2882 
2883         if (kd1.isValid() && kd1.timeZone() != QTimeZone::utc()) {
2884             zones << kd1.timeZone();
2885         }
2886 
2887         if (kd2.isValid() && kd2.timeZone() != QTimeZone::utc() && kd1.timeZone() != kd2.timeZone()) {
2888             zones << kd2.timeZone();
2889         }
2890 
2891         TimeZoneEarliestDate earliestTz;
2892         ICalTimeZoneParser::updateTzEarliestDate(incidence, &earliestTz);
2893 
2894         for (const auto &qtz : std::as_const(zones)) {
2895             icaltimezone *icaltz = ICalTimeZoneParser::icaltimezoneFromQTimeZone(qtz, earliestTz[qtz]);
2896             if (!icaltz) {
2897                 qCritical() << "bad time zone";
2898             } else {
2899                 icalcomponent *tz = icalcomponent_new_clone(icaltimezone_get_component(icaltz));
2900                 icalcomponent_add_component(message, tz);
2901                 icaltimezone_free(icaltz, 1);
2902             }
2903         }
2904     } else {
2905         qCDebug(KCALCORE_LOG) << "No incidence";
2906         return message;
2907     }
2908 
2909     icalproperty_method icalmethod = ICAL_METHOD_NONE;
2910 
2911     switch (method) {
2912     case iTIPPublish:
2913         icalmethod = ICAL_METHOD_PUBLISH;
2914         break;
2915     case iTIPRequest:
2916         icalmethod = ICAL_METHOD_REQUEST;
2917         break;
2918     case iTIPRefresh:
2919         icalmethod = ICAL_METHOD_REFRESH;
2920         break;
2921     case iTIPCancel:
2922         icalmethod = ICAL_METHOD_CANCEL;
2923         break;
2924     case iTIPAdd:
2925         icalmethod = ICAL_METHOD_ADD;
2926         break;
2927     case iTIPReply:
2928         icalmethod = ICAL_METHOD_REPLY;
2929         break;
2930     case iTIPCounter:
2931         icalmethod = ICAL_METHOD_COUNTER;
2932         break;
2933     case iTIPDeclineCounter:
2934         icalmethod = ICAL_METHOD_DECLINECOUNTER;
2935         break;
2936     default:
2937         qCDebug(KCALCORE_LOG) << "Unknown method";
2938         return message;
2939     }
2940 
2941     icalcomponent_add_property(message, icalproperty_new_method(icalmethod));
2942 
2943     icalcomponent *inc = writeIncidence(incidence, method);
2944 
2945     if (method != KCalendarCore::iTIPNoMethod) {
2946         // Not very nice, but since dtstamp changes semantics if used in scheduling, we have to adapt
2947         icalcomponent_set_dtstamp(inc, writeICalUtcDateTime(QDateTime::currentDateTimeUtc()));
2948     }
2949 
2950     /*
2951      * RFC 2446 states in section 3.4.3 ( REPLY to a VTODO ), that
2952      * a REQUEST-STATUS property has to be present. For the other two, event and
2953      * free busy, it can be there, but is optional. Until we do more
2954      * fine grained handling, assume all is well. Note that this is the
2955      * status of the _request_, not the attendee. Just to avoid confusion.
2956      * - till
2957      */
2958     if (icalmethod == ICAL_METHOD_REPLY) {
2959         struct icalreqstattype rst;
2960         rst.code = ICAL_2_0_SUCCESS_STATUS;
2961         rst.desc = nullptr;
2962         rst.debug = nullptr;
2963         icalcomponent_add_property(inc, icalproperty_new_requeststatus(rst));
2964     }
2965     icalcomponent_add_component(message, inc);
2966 
2967     return message;
2968 }