File indexing completed on 2024-04-28 11:34:08

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(QVector<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, {}, Qt::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 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 89)
1698             incidence->setHasGeo(true);
1699 #endif
1700             break;
1701         }
1702 
1703         case ICAL_PRIORITY_PROPERTY: // priority
1704             intvalue = icalproperty_get_priority(p);
1705             if (mCompat) {
1706                 intvalue = mCompat->fixPriority(intvalue);
1707             }
1708             incidence->setPriority(intvalue);
1709             break;
1710 
1711         case ICAL_CATEGORIES_PROPERTY: { // categories
1712             // We have always supported multiple CATEGORIES properties per component
1713             // even though the RFC seems to indicate only 1 is permitted.
1714             // We can't change that -- in order to retain backwards compatibility.
1715             text = icalproperty_get_categories(p);
1716             const QString val = QString::fromUtf8(text);
1717             const QStringList lstVal = val.split(QLatin1Char(','), Qt::SkipEmptyParts);
1718             for (const QString &cat : lstVal) {
1719                 // ensure no duplicates
1720                 if (!categories.contains(cat)) {
1721                     categories.append(cat);
1722                 }
1723             }
1724             break;
1725         }
1726 
1727         case ICAL_RECURRENCEID_PROPERTY: // recurrenceId
1728             kdt = readICalDateTimeProperty(p, tzlist);
1729             if (kdt.isValid()) {
1730                 incidence->setRecurrenceId(kdt);
1731                 const icalparameter *param = icalproperty_get_first_parameter(p, ICAL_RANGE_PARAMETER);
1732                 if (param && icalparameter_get_range(param) == ICAL_RANGE_THISANDFUTURE) {
1733                     incidence->setThisAndFuture(true);
1734                 } else {
1735                     // A workaround for a bug in libical (https://github.com/libical/libical/issues/185)
1736                     // If a recurrenceId has both tzid and range, both parameters end up in the tzid.
1737                     // This results in invalid tzid's like: "Europe/Berlin;RANGE=THISANDFUTURE"
1738                     const icalparameter *param = icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER);
1739                     QString tzid = QString::fromLatin1(icalparameter_get_tzid(param));
1740                     const QStringList parts = tzid.toLower().split(QLatin1Char(';'));
1741                     if (parts.contains(QLatin1String("range=thisandfuture"))) {
1742                         incidence->setThisAndFuture(true);
1743                     }
1744                 }
1745             }
1746             break;
1747 
1748         case ICAL_RRULE_PROPERTY:
1749             readRecurrenceRule(p, incidence);
1750             break;
1751 
1752         case ICAL_RDATE_PROPERTY: {
1753             bool allDay = false;
1754             kdt = readICalDateTimeProperty(p, tzlist, false, &allDay);
1755             if (kdt.isValid()) {
1756                 if (allDay) {
1757                     incidence->recurrence()->addRDate(kdt.date());
1758                 } else {
1759                     incidence->recurrence()->addRDateTime(kdt);
1760                 }
1761             } else {
1762                 icaldatetimeperiodtype tp = icalproperty_get_rdate(p);
1763                 const QDateTime start = readICalDateTime(p, tp.period.start, tzlist, false);
1764                 if (icaltime_is_null_time(tp.period.end)) {
1765                     Period period(start, readICalDuration(tp.period.duration));
1766                     incidence->recurrence()->addRDateTimePeriod(period);
1767                 } else {
1768                     Period period(start, readICalDateTime(p, tp.period.end, tzlist, false));
1769                     incidence->recurrence()->addRDateTimePeriod(period);
1770                 }
1771             }
1772             break;
1773         }
1774 
1775         case ICAL_EXRULE_PROPERTY:
1776             readExceptionRule(p, incidence);
1777             break;
1778 
1779         case ICAL_EXDATE_PROPERTY: {
1780             bool allDay = false;
1781             kdt = readICalDateTimeProperty(p, tzlist, false, &allDay);
1782             if (allDay) {
1783                 incidence->recurrence()->addExDate(kdt.date());
1784             } else {
1785                 incidence->recurrence()->addExDateTime(kdt);
1786             }
1787             break;
1788         }
1789 
1790         case ICAL_CLASS_PROPERTY:
1791             inttext = icalproperty_get_class(p);
1792             if (inttext == ICAL_CLASS_PUBLIC) {
1793                 incidence->setSecrecy(Incidence::SecrecyPublic);
1794             } else if (inttext == ICAL_CLASS_CONFIDENTIAL) {
1795                 incidence->setSecrecy(Incidence::SecrecyConfidential);
1796             } else {
1797                 incidence->setSecrecy(Incidence::SecrecyPrivate);
1798             }
1799             break;
1800 
1801         case ICAL_ATTACH_PROPERTY: // attachments
1802             incidence->addAttachment(readAttachment(p));
1803             break;
1804 
1805         case ICAL_COLOR_PROPERTY:
1806             incidence->setColor(QString::fromUtf8(icalproperty_get_color(p)));
1807             break;
1808 
1809         default:
1810             // TODO: do something about unknown properties?
1811             break;
1812         }
1813 
1814         p = icalcomponent_get_next_property(parent, ICAL_ANY_PROPERTY);
1815     }
1816 
1817     // Set the scheduling ID
1818     const QString uid = incidence->customProperty("LIBKCAL", "ID");
1819     if (!uid.isNull()) {
1820         // The UID stored in incidencebase is actually the scheduling ID
1821         // It has to be stored in the iCal UID component for compatibility
1822         // with other iCal applications
1823         incidence->setSchedulingID(incidence->uid(), uid);
1824     }
1825 
1826     // Now that recurrence and exception stuff is completely set up,
1827     // do any backwards compatibility adjustments.
1828     if (incidence->recurs() && mCompat) {
1829         mCompat->fixRecurrence(incidence);
1830     }
1831 
1832     // add categories
1833     incidence->setCategories(categories);
1834 
1835     // iterate through all alarms
1836     for (icalcomponent *alarm = icalcomponent_get_first_component(parent, ICAL_VALARM_COMPONENT); alarm;
1837          alarm = icalcomponent_get_next_component(parent, ICAL_VALARM_COMPONENT)) {
1838         readAlarm(alarm, incidence);
1839     }
1840 
1841     // iterate through all conferences
1842     Conference::List conferences;
1843     for (auto *conf = icalcomponent_get_first_property(parent, ICAL_CONFERENCE_PROPERTY); conf;
1844          conf = icalcomponent_get_next_property(parent, ICAL_CONFERENCE_PROPERTY)) {
1845         conferences.push_back(readConference(conf));
1846     }
1847     incidence->setConferences(conferences);
1848 
1849     if (mCompat) {
1850         // Fix incorrect alarm settings by other applications (like outloook 9)
1851         mCompat->fixAlarms(incidence);
1852         mCompat->setCreatedToDtStamp(incidence, dtstamp);
1853     }
1854 }
1855 
1856 //@cond PRIVATE
1857 void ICalFormatImpl::readIncidenceBase(icalcomponent *parent, const IncidenceBase::Ptr &incidenceBase)
1858 {
1859     icalproperty *p = icalcomponent_get_first_property(parent, ICAL_ANY_PROPERTY);
1860     bool uidProcessed = false;
1861     while (p) {
1862         icalproperty_kind kind = icalproperty_isa(p);
1863         switch (kind) {
1864         case ICAL_UID_PROPERTY: // unique id
1865             uidProcessed = true;
1866             incidenceBase->setUid(QString::fromUtf8(icalproperty_get_uid(p)));
1867             break;
1868 
1869         case ICAL_ORGANIZER_PROPERTY: // organizer
1870             incidenceBase->setOrganizer(readOrganizer(p));
1871             break;
1872 
1873         case ICAL_ATTENDEE_PROPERTY: // attendee
1874             incidenceBase->addAttendee(readAttendee(p));
1875             break;
1876 
1877         case ICAL_COMMENT_PROPERTY:
1878             incidenceBase->addComment(QString::fromUtf8(icalproperty_get_comment(p)));
1879             break;
1880 
1881         case ICAL_CONTACT_PROPERTY:
1882             incidenceBase->addContact(QString::fromUtf8(icalproperty_get_contact(p)));
1883             break;
1884 
1885         case ICAL_URL_PROPERTY:
1886             incidenceBase->setUrl(QUrl(QString::fromUtf8(icalproperty_get_url(p))));
1887             break;
1888 
1889         default:
1890             break;
1891         }
1892 
1893         p = icalcomponent_get_next_property(parent, ICAL_ANY_PROPERTY);
1894     }
1895 
1896     if (!uidProcessed) {
1897         qCWarning(KCALCORE_LOG) << "The incidence didn't have any UID! Report a bug "
1898                                 << "to the application that generated this file.";
1899 
1900         // Our in-memory incidence has a random uid generated in Event's ctor.
1901         // Generate a deterministic UID from its properties.
1902         // Otherwise, next time we read the file, this function will return
1903         // an event with another random uid and we will have two events in the calendar.
1904         std::vector<const char *> properties(icalcomponent_count_properties(parent, ICAL_ANY_PROPERTY));
1905         icalproperty *p = icalcomponent_get_first_property(parent, ICAL_ANY_PROPERTY);
1906         for (const char *&str : properties) {
1907             str = icalproperty_as_ical_string(p);
1908             p = icalcomponent_get_next_property(parent, ICAL_ANY_PROPERTY);
1909         }
1910         std::sort(properties.begin(), properties.end(),
1911                 [](const char *str1, const char *str2) {
1912                     return strcmp(str1, str2) < 0;
1913                 });
1914         QCryptographicHash hasher(QCryptographicHash::Md5);
1915         for (const char *str : properties) {
1916             hasher.addData(str);
1917         }
1918         incidenceBase->setUid(QString::fromLatin1(hasher.result().toHex()));
1919     }
1920 
1921     // custom properties
1922     readCustomProperties(parent, incidenceBase.data());
1923 }
1924 
1925 void ICalFormatImpl::readCustomProperties(icalcomponent *parent, CustomProperties *properties)
1926 {
1927     QByteArray property;
1928     QString value;
1929     QString parameters;
1930     icalproperty *p = icalcomponent_get_first_property(parent, ICAL_X_PROPERTY);
1931     icalparameter *param = nullptr;
1932 
1933     while (p) {
1934         QString nvalue = QString::fromUtf8(icalproperty_get_x(p));
1935         if (nvalue.isEmpty()) {
1936             icalvalue *value = icalproperty_get_value(p);
1937             if (icalvalue_isa(value) == ICAL_TEXT_VALUE) {
1938                 // Calling icalvalue_get_text( value ) on a datetime value crashes.
1939                 nvalue = QString::fromUtf8(icalvalue_get_text(value));
1940             } else {
1941                 nvalue = QString::fromUtf8(icalproperty_get_value_as_string(p));
1942             }
1943         }
1944         const char *name = icalproperty_get_x_name(p);
1945         QByteArray nproperty(name);
1946         if (property != nproperty) {
1947             // New property
1948             if (!property.isEmpty()) {
1949                 properties->setNonKDECustomProperty(property, value, parameters);
1950             }
1951             property = name;
1952             value = nvalue;
1953             QStringList parametervalues;
1954             for (param = icalproperty_get_first_parameter(p, ICAL_ANY_PARAMETER); param; param = icalproperty_get_next_parameter(p, ICAL_ANY_PARAMETER)) {
1955                 // 'c' is owned by ical library => all we need to do is just use it
1956                 const char *c = icalparameter_as_ical_string(param);
1957                 parametervalues.push_back(QLatin1String(c));
1958             }
1959             parameters = parametervalues.join(QLatin1Char(';'));
1960         } else {
1961             value = value.append(QLatin1Char(',')).append(nvalue);
1962         }
1963         p = icalcomponent_get_next_property(parent, ICAL_X_PROPERTY);
1964     }
1965     if (!property.isEmpty()) {
1966         properties->setNonKDECustomProperty(property, value, parameters);
1967     }
1968 }
1969 //@endcond
1970 
1971 void ICalFormatImpl::readRecurrenceRule(icalproperty *rrule, const Incidence::Ptr &incidence)
1972 {
1973     Recurrence *recur = incidence->recurrence();
1974 
1975     struct icalrecurrencetype r = icalproperty_get_rrule(rrule);
1976     // dumpIcalRecurrence(r);
1977 
1978     RecurrenceRule *recurrule = new RecurrenceRule(/*incidence*/);
1979     recurrule->setStartDt(incidence->dtStart());
1980     readRecurrence(r, recurrule);
1981     recur->addRRule(recurrule);
1982 }
1983 
1984 void ICalFormatImpl::readExceptionRule(icalproperty *rrule, const Incidence::Ptr &incidence)
1985 {
1986     struct icalrecurrencetype r = icalproperty_get_exrule(rrule);
1987     // dumpIcalRecurrence(r);
1988 
1989     RecurrenceRule *recurrule = new RecurrenceRule(/*incidence*/);
1990     recurrule->setStartDt(incidence->dtStart());
1991     readRecurrence(r, recurrule);
1992 
1993     Recurrence *recur = incidence->recurrence();
1994     recur->addExRule(recurrule);
1995 }
1996 
1997 void ICalFormatImpl::readRecurrence(const struct icalrecurrencetype &r, RecurrenceRule *recur)
1998 {
1999     // Generate the RRULE string
2000     recur->setRRule(QLatin1String(icalrecurrencetype_as_string(const_cast<struct icalrecurrencetype *>(&r))));
2001     // Period
2002     switch (r.freq) {
2003     case ICAL_SECONDLY_RECURRENCE:
2004         recur->setRecurrenceType(RecurrenceRule::rSecondly);
2005         break;
2006     case ICAL_MINUTELY_RECURRENCE:
2007         recur->setRecurrenceType(RecurrenceRule::rMinutely);
2008         break;
2009     case ICAL_HOURLY_RECURRENCE:
2010         recur->setRecurrenceType(RecurrenceRule::rHourly);
2011         break;
2012     case ICAL_DAILY_RECURRENCE:
2013         recur->setRecurrenceType(RecurrenceRule::rDaily);
2014         break;
2015     case ICAL_WEEKLY_RECURRENCE:
2016         recur->setRecurrenceType(RecurrenceRule::rWeekly);
2017         break;
2018     case ICAL_MONTHLY_RECURRENCE:
2019         recur->setRecurrenceType(RecurrenceRule::rMonthly);
2020         break;
2021     case ICAL_YEARLY_RECURRENCE:
2022         recur->setRecurrenceType(RecurrenceRule::rYearly);
2023         break;
2024     case ICAL_NO_RECURRENCE:
2025     default:
2026         recur->setRecurrenceType(RecurrenceRule::rNone);
2027     }
2028     // Frequency
2029     recur->setFrequency(r.interval);
2030 
2031     // Duration & End Date
2032     if (!icaltime_is_null_time(r.until)) {
2033         icaltimetype t = r.until;
2034         recur->setEndDt(readICalUtcDateTime(nullptr, t));
2035     } else {
2036         if (r.count == 0) {
2037             recur->setDuration(-1);
2038         } else {
2039             recur->setDuration(r.count);
2040         }
2041     }
2042 
2043     // Week start setting
2044     short wkst = static_cast<short>((r.week_start + 5) % 7 + 1);
2045     recur->setWeekStart(wkst);
2046 
2047     // And now all BY*
2048     QList<int> lst;
2049     int i;
2050     int index = 0;
2051 
2052 // clang-format off
2053 //@cond PRIVATE
2054 #define readSetByList( rrulecomp, setfunc )                             \
2055     index = 0;                                                            \
2056     lst.clear();                                                          \
2057     while ( ( i = r.rrulecomp[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { \
2058         lst.append( i );                                                    \
2059     }                                                                     \
2060     if ( !lst.isEmpty() ) {                                               \
2061         recur->setfunc( lst );                                              \
2062     }
2063 //@endcond
2064     // clang-format on
2065 
2066     // BYSECOND, MINUTE and HOUR, MONTHDAY, YEARDAY, WEEKNUMBER, MONTH
2067     // and SETPOS are standard int lists, so we can treat them with the
2068     // same macro
2069     readSetByList(by_second, setBySeconds);
2070     readSetByList(by_minute, setByMinutes);
2071     readSetByList(by_hour, setByHours);
2072     readSetByList(by_month_day, setByMonthDays);
2073     readSetByList(by_year_day, setByYearDays);
2074     readSetByList(by_week_no, setByWeekNumbers);
2075     readSetByList(by_month, setByMonths);
2076     readSetByList(by_set_pos, setBySetPos);
2077 #undef readSetByList
2078 
2079     // BYDAY is a special case, since it's not an int list
2080     QList<RecurrenceRule::WDayPos> wdlst;
2081     short day;
2082     index = 0;
2083     while ((day = r.by_day[index++]) != ICAL_RECURRENCE_ARRAY_MAX) {
2084         RecurrenceRule::WDayPos pos;
2085         pos.setDay(static_cast<short>((icalrecurrencetype_day_day_of_week(day) + 5) % 7 + 1));
2086         pos.setPos(icalrecurrencetype_day_position(day));
2087         wdlst.append(pos);
2088     }
2089     if (!wdlst.isEmpty()) {
2090         recur->setByDays(wdlst);
2091     }
2092 
2093     // TODO: Store all X- fields of the RRULE inside the recurrence (so they are
2094     // preserved
2095 }
2096 
2097 void ICalFormatImpl::readAlarm(icalcomponent *alarm, const Incidence::Ptr &incidence)
2098 {
2099     Alarm::Ptr ialarm = incidence->newAlarm();
2100     ialarm->setRepeatCount(0);
2101     ialarm->setEnabled(true);
2102 
2103     // Determine the alarm's action type
2104     icalproperty *p = icalcomponent_get_first_property(alarm, ICAL_ACTION_PROPERTY);
2105     Alarm::Type type = Alarm::Display;
2106     icalproperty_action action = ICAL_ACTION_DISPLAY;
2107     if (!p) {
2108         qCDebug(KCALCORE_LOG) << "Unknown type of alarm, using default";
2109         // TODO: do something about unknown alarm type?
2110     } else {
2111         action = icalproperty_get_action(p);
2112         switch (action) {
2113         case ICAL_ACTION_DISPLAY:
2114             type = Alarm::Display;
2115             break;
2116         case ICAL_ACTION_AUDIO:
2117             type = Alarm::Audio;
2118             break;
2119         case ICAL_ACTION_PROCEDURE:
2120             type = Alarm::Procedure;
2121             break;
2122         case ICAL_ACTION_EMAIL:
2123             type = Alarm::Email;
2124             break;
2125         default:
2126             break;
2127             // TODO: do something about invalid alarm type?
2128         }
2129     }
2130     ialarm->setType(type);
2131 
2132     p = icalcomponent_get_first_property(alarm, ICAL_ANY_PROPERTY);
2133     while (p) {
2134         icalproperty_kind kind = icalproperty_isa(p);
2135 
2136         switch (kind) {
2137         case ICAL_TRIGGER_PROPERTY: {
2138             icaltriggertype trigger = icalproperty_get_trigger(p);
2139             if (!icaltime_is_null_time(trigger.time)) {
2140                 // set the trigger to a specific time (which is not in rfc2445, btw)
2141                 ialarm->setTime(readICalUtcDateTime(p, trigger.time));
2142             } else {
2143                 // set the trigger to an offset from the incidence start or end time.
2144                 if (!icaldurationtype_is_bad_duration(trigger.duration)) {
2145                     Duration duration(readICalDuration(trigger.duration));
2146                     icalparameter *param = icalproperty_get_first_parameter(p, ICAL_RELATED_PARAMETER);
2147                     if (param && icalparameter_get_related(param) == ICAL_RELATED_END) {
2148                         ialarm->setEndOffset(duration);
2149                     } else {
2150                         ialarm->setStartOffset(duration);
2151                     }
2152                 } else {
2153                     // a bad duration was encountered, just set a 0 duration from start
2154                     ialarm->setStartOffset(Duration(0));
2155                 }
2156             }
2157             break;
2158         }
2159         case ICAL_DURATION_PROPERTY: {
2160             icaldurationtype duration = icalproperty_get_duration(p);
2161             ialarm->setSnoozeTime(readICalDuration(duration));
2162             break;
2163         }
2164         case ICAL_REPEAT_PROPERTY:
2165             ialarm->setRepeatCount(icalproperty_get_repeat(p));
2166             break;
2167 
2168         case ICAL_DESCRIPTION_PROPERTY: {
2169             // Only in DISPLAY and EMAIL and PROCEDURE alarms
2170             QString description = QString::fromUtf8(icalproperty_get_description(p));
2171             switch (action) {
2172             case ICAL_ACTION_DISPLAY:
2173                 ialarm->setText(description);
2174                 break;
2175             case ICAL_ACTION_PROCEDURE:
2176                 ialarm->setProgramArguments(description);
2177                 break;
2178             case ICAL_ACTION_EMAIL:
2179                 ialarm->setMailText(description);
2180                 break;
2181             default:
2182                 break;
2183             }
2184             break;
2185         }
2186         case ICAL_SUMMARY_PROPERTY:
2187             // Only in EMAIL alarm
2188             ialarm->setMailSubject(QString::fromUtf8(icalproperty_get_summary(p)));
2189             break;
2190 
2191         case ICAL_ATTENDEE_PROPERTY: {
2192             // Only in EMAIL alarm
2193             QString email = QString::fromUtf8(icalproperty_get_attendee(p));
2194             if (email.startsWith(QLatin1String("mailto:"), Qt::CaseInsensitive)) {
2195                 email.remove(0, 7);
2196             }
2197             QString name;
2198             icalparameter *param = icalproperty_get_first_parameter(p, ICAL_CN_PARAMETER);
2199             if (param) {
2200                 name = QString::fromUtf8(icalparameter_get_cn(param));
2201             }
2202             ialarm->addMailAddress(Person(name, email));
2203             break;
2204         }
2205 
2206         case ICAL_ATTACH_PROPERTY: {
2207             // Only in AUDIO and EMAIL and PROCEDURE alarms
2208             Attachment attach = readAttachment(p);
2209             if (!attach.isEmpty() && attach.isUri()) {
2210                 switch (action) {
2211                 case ICAL_ACTION_AUDIO:
2212                     ialarm->setAudioFile(attach.uri());
2213                     break;
2214                 case ICAL_ACTION_PROCEDURE:
2215                     ialarm->setProgramFile(attach.uri());
2216                     break;
2217                 case ICAL_ACTION_EMAIL:
2218                     ialarm->addMailAttachment(attach.uri());
2219                     break;
2220                 default:
2221                     break;
2222                 }
2223             } else {
2224                 qCDebug(KCALCORE_LOG) << "Alarm attachments currently only support URIs,"
2225                                       << "but no binary data";
2226             }
2227             break;
2228         }
2229         default:
2230             break;
2231         }
2232         p = icalcomponent_get_next_property(alarm, ICAL_ANY_PROPERTY);
2233     }
2234 
2235     // custom properties
2236     readCustomProperties(alarm, ialarm.data());
2237 
2238     QString locationRadius = ialarm->nonKDECustomProperty("X-LOCATION-RADIUS");
2239     if (!locationRadius.isEmpty()) {
2240         ialarm->setLocationRadius(locationRadius.toInt());
2241         ialarm->setHasLocationRadius(true);
2242     }
2243 
2244     if (ialarm->customProperty(APP_NAME_FOR_XPROPERTIES, ENABLED_ALARM_XPROPERTY) == QLatin1String("FALSE")) {
2245         ialarm->setEnabled(false);
2246     }
2247     // TODO: check for consistency of alarm properties
2248 }
2249 
2250 icaldatetimeperiodtype ICalFormatImpl::writeICalDatePeriod(const QDate &date)
2251 {
2252     icaldatetimeperiodtype t;
2253     t.time = writeICalDate(date);
2254     t.period = icalperiodtype_null_period();
2255     return t;
2256 }
2257 
2258 Conference ICalFormatImpl::readConference(icalproperty *prop)
2259 {
2260     Conference conf;
2261     conf.setUri(QUrl(QString::fromUtf8(icalproperty_get_conference(prop))));
2262     conf.setLabel(QString::fromUtf8(icalproperty_get_parameter_as_string(prop, "LABEL")));
2263     conf.setFeatures(QString::fromUtf8(icalproperty_get_parameter_as_string(prop, "FEATURE")).split(QLatin1Char(',')));
2264     conf.setLanguage(QString::fromUtf8(icalproperty_get_parameter_as_string(prop, "LANGUAGE")));
2265     return conf;
2266 }
2267 
2268 icaltimetype ICalFormatImpl::writeICalDate(const QDate &date)
2269 {
2270     icaltimetype t = icaltime_null_time();
2271 
2272     t.year = date.year();
2273     t.month = date.month();
2274     t.day = date.day();
2275 
2276     t.hour = 0;
2277     t.minute = 0;
2278     t.second = 0;
2279 
2280     t.is_date = 1;
2281     t.zone = nullptr;
2282 
2283     return t;
2284 }
2285 
2286 static bool dateTimeIsInUTC(const QDateTime &datetime)
2287 {
2288     return datetime.timeSpec() == Qt::UTC || (datetime.timeSpec() == Qt::TimeZone && datetime.timeZone() == QTimeZone::utc())
2289         || (datetime.timeSpec() == Qt::OffsetFromUTC && datetime.offsetFromUtc() == 0);
2290 }
2291 
2292 icaltimetype ICalFormatImpl::writeICalDateTime(const QDateTime &datetime, bool dateOnly)
2293 {
2294     icaltimetype t = icaltime_null_time();
2295 
2296     t.year = datetime.date().year();
2297     t.month = datetime.date().month();
2298     t.day = datetime.date().day();
2299 
2300     t.is_date = dateOnly;
2301 
2302     if (!t.is_date) {
2303         t.hour = datetime.time().hour();
2304         t.minute = datetime.time().minute();
2305         t.second = datetime.time().second();
2306     }
2307     t.zone = nullptr; // zone is NOT set
2308     if (dateTimeIsInUTC(datetime)) {
2309         t = icaltime_convert_to_zone(t, icaltimezone_get_utc_timezone());
2310     }
2311     return t;
2312 }
2313 
2314 icalproperty *ICalFormatImpl::writeICalDateTimeProperty(const icalproperty_kind type, const QDateTime &dt, TimeZoneList *tzUsedList)
2315 {
2316     icaltimetype t;
2317 
2318     switch (type) {
2319     case ICAL_DTSTAMP_PROPERTY:
2320     case ICAL_CREATED_PROPERTY:
2321     case ICAL_LASTMODIFIED_PROPERTY:
2322         t = writeICalDateTime(dt.toUTC());
2323         break;
2324     default:
2325         t = writeICalDateTime(dt);
2326         break;
2327     }
2328 
2329     icalproperty *p;
2330     switch (type) {
2331     case ICAL_DTSTAMP_PROPERTY:
2332         p = icalproperty_new_dtstamp(t);
2333         break;
2334     case ICAL_CREATED_PROPERTY:
2335         p = icalproperty_new_created(t);
2336         break;
2337     case ICAL_LASTMODIFIED_PROPERTY:
2338         p = icalproperty_new_lastmodified(t);
2339         break;
2340     case ICAL_DTSTART_PROPERTY: // start date and time
2341         p = icalproperty_new_dtstart(t);
2342         break;
2343     case ICAL_DTEND_PROPERTY: // end date and time
2344         p = icalproperty_new_dtend(t);
2345         break;
2346     case ICAL_DUE_PROPERTY:
2347         p = icalproperty_new_due(t);
2348         break;
2349     case ICAL_RECURRENCEID_PROPERTY:
2350         p = icalproperty_new_recurrenceid(t);
2351         break;
2352     case ICAL_EXDATE_PROPERTY:
2353         p = icalproperty_new_exdate(t);
2354         break;
2355     case ICAL_X_PROPERTY: {
2356         p = icalproperty_new_x("");
2357         icaltimetype timeType = writeICalDateTime(dt);
2358         icalvalue *text = icalvalue_new_datetime(timeType);
2359         icalproperty_set_value(p, text);
2360     } break;
2361     default: {
2362         icaldatetimeperiodtype tp;
2363         tp.time = t;
2364         tp.period = icalperiodtype_null_period();
2365         switch (type) {
2366         case ICAL_RDATE_PROPERTY:
2367             p = icalproperty_new_rdate(tp);
2368             break;
2369         default:
2370             return nullptr;
2371         }
2372     }
2373     }
2374 
2375     QTimeZone qtz;
2376     if (!icaltime_is_utc(t) && !dateTimeIsInUTC(dt) && dt.timeSpec() != Qt::LocalTime) {
2377         qtz = dt.timeZone();
2378     }
2379 
2380     if (qtz.isValid()) {
2381         if (tzUsedList) {
2382             if (!tzUsedList->contains(qtz)) {
2383                 tzUsedList->push_back(qtz);
2384             }
2385         }
2386 
2387         icalproperty_add_parameter(p, icalparameter_new_tzid(qtz.id().constData()));
2388     }
2389     return p;
2390 }
2391 
2392 QDateTime ICalFormatImpl::readICalDateTime(icalproperty *p, const icaltimetype &t, const ICalTimeZoneCache *tzCache, bool utc)
2393 {
2394     //  qCDebug(KCALCORE_LOG);
2395     //  _dumpIcaltime( t );
2396 
2397     QTimeZone timeZone;
2398     if (icaltime_is_utc(t) || t.zone == icaltimezone_get_utc_timezone()) {
2399         timeZone = QTimeZone::utc(); // the time zone is UTC
2400         utc = false; // no need to convert to UTC
2401     } else {
2402         icalparameter *param = p ? icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) : nullptr;
2403         QByteArray tzid = param ? QByteArray(icalparameter_get_tzid(param)) : QByteArray();
2404 
2405         // A workaround for a bug in libical (https://github.com/libical/libical/issues/185)
2406         // If a recurrenceId has both tzid and range, both parameters end up in the tzid.
2407         // This results in invalid tzid's like: "Europe/Berlin;RANGE=THISANDFUTURE"
2408         QStringList parts = QString::fromLatin1(tzid).split(QLatin1Char(';'));
2409         if (parts.count() > 1) {
2410             tzid = parts.first().toLatin1();
2411         }
2412 
2413         if (tzCache) {
2414             // First try to get the timezone from cache
2415             timeZone = tzCache->tzForTime(QDateTime({t.year, t.month, t.day}, {}), tzid);
2416         }
2417         if (!timeZone.isValid() && !tzid.isEmpty()) {
2418             // Fallback to trying to match against Qt timezone
2419             timeZone = QTimeZone(tzid);
2420         }
2421         // If Time zone is still invalid, we will use LocalTime as TimeSpec.
2422     }
2423     QTime resultTime;
2424     if (!t.is_date) {
2425         resultTime = QTime(t.hour, t.minute, t.second);
2426     }
2427     QDateTime result;
2428     if (timeZone.isValid()) {
2429         result = QDateTime(QDate(t.year, t.month, t.day), resultTime, timeZone);
2430     } else {
2431         result = QDateTime(QDate(t.year, t.month, t.day), resultTime);
2432     }
2433     return utc ? result.toUTC() : result;
2434 }
2435 
2436 QDate ICalFormatImpl::readICalDate(const icaltimetype &t)
2437 {
2438     return QDate(t.year, t.month, t.day);
2439 }
2440 
2441 QDateTime ICalFormatImpl::readICalDateTimeProperty(icalproperty *p, const ICalTimeZoneCache *tzList, bool utc, bool *allDay)
2442 {
2443     icaldatetimeperiodtype tp;
2444     icalproperty_kind kind = icalproperty_isa(p);
2445     switch (kind) {
2446     case ICAL_CREATED_PROPERTY: // UTC date/time
2447         tp.time = icalproperty_get_created(p);
2448         utc = true;
2449         break;
2450     case ICAL_DTSTAMP_PROPERTY: // UTC date/time
2451         tp.time = icalproperty_get_dtstamp(p);
2452         utc = true;
2453         break;
2454     case ICAL_LASTMODIFIED_PROPERTY: // last modification UTC date/time
2455         tp.time = icalproperty_get_lastmodified(p);
2456         utc = true;
2457         break;
2458     case ICAL_DTSTART_PROPERTY: // start date and time (UTC for freebusy)
2459         tp.time = icalproperty_get_dtstart(p);
2460         break;
2461     case ICAL_DTEND_PROPERTY: // end date and time (UTC for freebusy)
2462         tp.time = icalproperty_get_dtend(p);
2463         break;
2464     case ICAL_DUE_PROPERTY: // due date/time
2465         tp.time = icalproperty_get_due(p);
2466         break;
2467     case ICAL_COMPLETED_PROPERTY: // UTC completion date/time
2468         tp.time = icalproperty_get_completed(p);
2469         utc = true;
2470         break;
2471     case ICAL_RECURRENCEID_PROPERTY:
2472         tp.time = icalproperty_get_recurrenceid(p);
2473         break;
2474     case ICAL_EXDATE_PROPERTY:
2475         tp.time = icalproperty_get_exdate(p);
2476         break;
2477     case ICAL_X_PROPERTY: {
2478         const char *name = icalproperty_get_x_name(p);
2479         if (QLatin1String(name) == QLatin1String("X-KDE-LIBKCAL-DTRECURRENCE")) {
2480             const char *value = icalvalue_as_ical_string(icalproperty_get_value(p));
2481             icalvalue *v = icalvalue_new_from_string(ICAL_DATETIME_VALUE, value);
2482             tp.time = icalvalue_get_datetime(v);
2483             icalvalue_free(v);
2484             break;
2485         }
2486     } // end of ICAL_X_PROPERTY
2487         Q_FALLTHROUGH();
2488     default:
2489         switch (kind) {
2490         case ICAL_RDATE_PROPERTY:
2491             tp = icalproperty_get_rdate(p);
2492             break;
2493         default:
2494             return QDateTime();
2495         }
2496         if (!icaltime_is_valid_time(tp.time)) {
2497             return QDateTime(); // a time period was found (not implemented yet)
2498         }
2499         break;
2500     }
2501 
2502     if (allDay) {
2503         *allDay = tp.time.is_date;
2504     }
2505 
2506     if (tp.time.is_date) {
2507         return QDateTime(readICalDate(tp.time), QTime());
2508     } else {
2509         return readICalDateTime(p, tp.time, tzList, utc);
2510     }
2511 }
2512 
2513 icaldurationtype ICalFormatImpl::writeICalDuration(const Duration &duration)
2514 {
2515     // should be able to use icaldurationtype_from_int(), except we know
2516     // that some older tools do not properly support weeks. So we never
2517     // set a week duration, only days
2518 
2519     icaldurationtype d;
2520 
2521     int value = duration.value();
2522     d.is_neg = (value < 0) ? 1 : 0;
2523     if (value < 0) {
2524         value = -value;
2525     }
2526     // RFC2445 states that an ical duration value must be
2527     // EITHER weeks OR days/time, not both.
2528     if (duration.isDaily()) {
2529         if (!(value % 7)) {
2530             d.weeks = value / 7;
2531             d.days = 0;
2532         } else {
2533             d.weeks = 0;
2534             d.days = value;
2535         }
2536         d.hours = d.minutes = d.seconds = 0;
2537     } else {
2538         if (!(value % gSecondsPerWeek)) {
2539             d.weeks = value / gSecondsPerWeek;
2540             d.days = d.hours = d.minutes = d.seconds = 0;
2541         } else {
2542             d.weeks = 0;
2543             d.days = value / gSecondsPerDay;
2544             value %= gSecondsPerDay;
2545             d.hours = value / gSecondsPerHour;
2546             value %= gSecondsPerHour;
2547             d.minutes = value / gSecondsPerMinute;
2548             value %= gSecondsPerMinute;
2549             d.seconds = value;
2550         }
2551     }
2552 
2553     return d;
2554 }
2555 
2556 Duration ICalFormatImpl::readICalDuration(const icaldurationtype &d)
2557 {
2558     int days = d.weeks * 7;
2559     days += d.days;
2560     int seconds = d.hours * gSecondsPerHour;
2561     seconds += d.minutes * gSecondsPerMinute;
2562     seconds += d.seconds;
2563     if (seconds || !days) { // Create second-type duration for 0 delay durations.
2564         seconds += days * gSecondsPerDay;
2565         if (d.is_neg) {
2566             seconds = -seconds;
2567         }
2568         return Duration(seconds, Duration::Seconds);
2569     } else {
2570         if (d.is_neg) {
2571             days = -days;
2572         }
2573         return Duration(days, Duration::Days);
2574     }
2575 }
2576 
2577 icalcomponent *ICalFormatImpl::createCalendarComponent(const Calendar::Ptr &cal)
2578 {
2579     icalcomponent *calendar;
2580 
2581     // Root component
2582     calendar = icalcomponent_new(ICAL_VCALENDAR_COMPONENT);
2583 
2584     // Product Identifier
2585     icalproperty *p = icalproperty_new_prodid(CalFormat::productId().toUtf8().constData());
2586     icalcomponent_add_property(calendar, p);
2587 
2588     // iCalendar version (2.0)
2589     p = icalproperty_new_version(const_cast<char *>(_ICAL_VERSION));
2590     icalcomponent_add_property(calendar, p);
2591 
2592     // Implementation Version
2593     p = icalproperty_new_x(_ICAL_IMPLEMENTATION_VERSION);
2594     icalproperty_set_x_name(p, IMPLEMENTATION_VERSION_XPROPERTY);
2595     icalcomponent_add_property(calendar, p);
2596 
2597     // Add time zone
2598     // NOTE: Commented out since relevant timezones are added by the caller.
2599     // Previously we got some timezones listed twice in the ical file.
2600     /*
2601     if ( cal && cal->timeZones() ) {
2602       const ICalTimeZones::ZoneMap zmaps = cal->timeZones()->zones();
2603       for ( ICalTimeZones::ZoneMap::ConstIterator it=zmaps.constBegin();
2604             it != zmaps.constEnd(); ++it ) {
2605         icaltimezone *icaltz = (*it).icalTimezone();
2606         if ( !icaltz ) {
2607           qCritical() << "bad time zone";
2608         } else {
2609           icalcomponent *tz = icalcomponent_new_clone( icaltimezone_get_component( icaltz ) );
2610           icalcomponent_add_component( calendar, tz );
2611           icaltimezone_free( icaltz, 1 );
2612         }
2613       }
2614     }
2615     */
2616     // Custom properties
2617     if (cal != nullptr) {
2618         writeCustomProperties(calendar, cal.data());
2619     }
2620 
2621     return calendar;
2622 }
2623 
2624 Incidence::Ptr ICalFormatImpl::readOneIncidence(icalcomponent *calendar, const ICalTimeZoneCache *tzlist)
2625 {
2626     if (!calendar) {
2627         qCWarning(KCALCORE_LOG) << "Populate called with empty calendar";
2628         return Incidence::Ptr();
2629     }
2630     icalcomponent *c = icalcomponent_get_first_component(calendar, ICAL_VEVENT_COMPONENT);
2631     if (c) {
2632         return readEvent(c, tzlist);
2633     }
2634     c = icalcomponent_get_first_component(calendar, ICAL_VTODO_COMPONENT);
2635     if (c) {
2636         return readTodo(c, tzlist);
2637     }
2638     c = icalcomponent_get_first_component(calendar, ICAL_VJOURNAL_COMPONENT);
2639     if (c) {
2640         return readJournal(c, tzlist);
2641     }
2642     qCWarning(KCALCORE_LOG) << "Found no incidence";
2643     return Incidence::Ptr();
2644 }
2645 
2646 // take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc.
2647 // and break it down from its tree-like format into the dictionary format
2648 // that is used internally in the ICalFormatImpl.
2649 bool ICalFormatImpl::populate(const Calendar::Ptr &cal, icalcomponent *calendar, bool deleted, const QString &notebook)
2650 {
2651     // qCDebug(KCALCORE_LOG)<<"Populate called";
2652 
2653     // this function will populate the caldict dictionary and other event
2654     // lists. It turns vevents into Events and then inserts them.
2655 
2656     if (!calendar) {
2657         qCWarning(KCALCORE_LOG) << "Populate called with empty calendar";
2658         return false;
2659     }
2660 
2661     // TODO: check for METHOD
2662 
2663     icalproperty *p = icalcomponent_get_first_property(calendar, ICAL_X_PROPERTY);
2664     QString implementationVersion;
2665 
2666     while (p) {
2667         const char *name = icalproperty_get_x_name(p);
2668         QByteArray nproperty(name);
2669         if (nproperty == QByteArray(IMPLEMENTATION_VERSION_XPROPERTY)) {
2670             QString nvalue = QString::fromUtf8(icalproperty_get_x(p));
2671             if (nvalue.isEmpty()) {
2672                 icalvalue *value = icalproperty_get_value(p);
2673                 if (icalvalue_isa(value) == ICAL_TEXT_VALUE) {
2674                     nvalue = QString::fromUtf8(icalvalue_get_text(value));
2675                 }
2676             }
2677             implementationVersion = nvalue;
2678             icalcomponent_remove_property(calendar, p);
2679             icalproperty_free(p);
2680         }
2681         p = icalcomponent_get_next_property(calendar, ICAL_X_PROPERTY);
2682     }
2683 
2684     p = icalcomponent_get_first_property(calendar, ICAL_PRODID_PROPERTY);
2685     if (!p) {
2686         qCDebug(KCALCORE_LOG) << "No PRODID property found";
2687         mLoadedProductId.clear();
2688     } else {
2689         mLoadedProductId = QString::fromUtf8(icalproperty_get_prodid(p));
2690 
2691         mCompat.reset(CompatFactory::createCompat(mLoadedProductId, implementationVersion));
2692     }
2693 
2694     p = icalcomponent_get_first_property(calendar, ICAL_VERSION_PROPERTY);
2695     if (!p) {
2696         qCDebug(KCALCORE_LOG) << "No VERSION property found";
2697         mParent->setException(new Exception(Exception::CalVersionUnknown));
2698         return false;
2699     } else {
2700         const char *version = icalproperty_get_version(p);
2701         if (!version) {
2702             qCDebug(KCALCORE_LOG) << "No VERSION property found";
2703             mParent->setException(new Exception(Exception::VersionPropertyMissing));
2704 
2705             return false;
2706         }
2707         if (strcmp(version, "1.0") == 0) {
2708             qCDebug(KCALCORE_LOG) << "Expected iCalendar, got vCalendar";
2709             mParent->setException(new Exception(Exception::CalVersion1));
2710             return false;
2711         } else if (strcmp(version, "2.0") != 0) {
2712             qCDebug(KCALCORE_LOG) << "Expected iCalendar, got unknown format";
2713             mParent->setException(new Exception(Exception::CalVersionUnknown));
2714             return false;
2715         }
2716     }
2717 
2718     // Populate the calendar's time zone collection with all VTIMEZONE components
2719     ICalTimeZoneCache timeZoneCache;
2720     ICalTimeZoneParser parser(&timeZoneCache);
2721     parser.parse(calendar);
2722 
2723     // custom properties
2724     readCustomProperties(calendar, cal.data());
2725 
2726     // Store all events with a relatedTo property in a list for post-processing
2727     mEventsRelate.clear();
2728     mTodosRelate.clear();
2729     // TODO: make sure that only actually added events go to this lists.
2730 
2731     icalcomponent *c = icalcomponent_get_first_component(calendar, ICAL_VTODO_COMPONENT);
2732     while (c) {
2733         Todo::Ptr todo = readTodo(c, &timeZoneCache);
2734         if (todo) {
2735             // qCDebug(KCALCORE_LOG) << "todo is not zero and deleted is " << deleted;
2736             Todo::Ptr old = cal->todo(todo->uid(), todo->recurrenceId());
2737             if (old) {
2738                 if (old->uid().isEmpty()) {
2739                     qCWarning(KCALCORE_LOG) << "Skipping invalid VTODO";
2740                     c = icalcomponent_get_next_component(calendar, ICAL_VTODO_COMPONENT);
2741                     continue;
2742                 }
2743                 // qCDebug(KCALCORE_LOG) << "Found an old todo with uid " << old->uid();
2744                 if (deleted) {
2745                     // qCDebug(KCALCORE_LOG) << "Todo " << todo->uid() << " already deleted";
2746                     cal->deleteTodo(old); // move old to deleted
2747                     removeAllICal(mTodosRelate, old);
2748                 } else if (todo->revision() > old->revision()) {
2749                     // qCDebug(KCALCORE_LOG) << "Replacing old todo " << old.data() << " with this one " << todo.data();
2750                     cal->deleteTodo(old); // move old to deleted
2751                     removeAllICal(mTodosRelate, old);
2752                     cal->addTodo(todo); // and replace it with this one
2753                 }
2754             } else if (deleted) {
2755                 // qCDebug(KCALCORE_LOG) << "Todo " << todo->uid() << " already deleted";
2756                 old = cal->deletedTodo(todo->uid(), todo->recurrenceId());
2757                 if (!old) {
2758                     cal->addTodo(todo); // add this one
2759                     cal->deleteTodo(todo); // and move it to deleted
2760                 }
2761             } else {
2762                 // qCDebug(KCALCORE_LOG) << "Adding todo " << todo.data() << todo->uid();
2763                 cal->addTodo(todo); // just add this one
2764             }
2765             if (!notebook.isEmpty() && cal->todo(todo->uid(), todo->recurrenceId())) {
2766                 cal->setNotebook(todo, notebook);
2767             }
2768         }
2769         c = icalcomponent_get_next_component(calendar, ICAL_VTODO_COMPONENT);
2770     }
2771 
2772     // Iterate through all events
2773     c = icalcomponent_get_first_component(calendar, ICAL_VEVENT_COMPONENT);
2774     while (c) {
2775         Event::Ptr event = readEvent(c, &timeZoneCache);
2776         if (event) {
2777             // qCDebug(KCALCORE_LOG) << "event is not zero and deleted is " << deleted;
2778             Event::Ptr old = cal->event(event->uid(), event->recurrenceId());
2779             if (old) {
2780                 if (old->uid().isEmpty()) {
2781                     qCWarning(KCALCORE_LOG) << "Skipping invalid VEVENT";
2782                     c = icalcomponent_get_next_component(calendar, ICAL_VEVENT_COMPONENT);
2783                     continue;
2784                 }
2785                 // qCDebug(KCALCORE_LOG) << "Found an old event with uid " << old->uid();
2786                 if (deleted) {
2787                     // qCDebug(KCALCORE_LOG) << "Event " << event->uid() << " already deleted";
2788                     cal->deleteEvent(old); // move old to deleted
2789                     removeAllICal(mEventsRelate, old);
2790                 } else if (event->revision() > old->revision()) {
2791                     // qCDebug(KCALCORE_LOG) << "Replacing old event " << old.data()
2792                     //                       << " with this one " << event.data();
2793                     cal->deleteEvent(old); // move old to deleted
2794                     removeAllICal(mEventsRelate, old);
2795                     cal->addEvent(event); // and replace it with this one
2796                 }
2797             } else if (deleted) {
2798                 // qCDebug(KCALCORE_LOG) << "Event " << event->uid() << " already deleted";
2799                 old = cal->deletedEvent(event->uid(), event->recurrenceId());
2800                 if (!old) {
2801                     cal->addEvent(event); // add this one
2802                     cal->deleteEvent(event); // and move it to deleted
2803                 }
2804             } else {
2805                 // qCDebug(KCALCORE_LOG) << "Adding event " << event.data() << event->uid();
2806                 cal->addEvent(event); // just add this one
2807             }
2808             if (!notebook.isEmpty() && cal->event(event->uid(), event->recurrenceId())) {
2809                 cal->setNotebook(event, notebook);
2810             }
2811         }
2812         c = icalcomponent_get_next_component(calendar, ICAL_VEVENT_COMPONENT);
2813     }
2814 
2815     // Iterate through all journals
2816     c = icalcomponent_get_first_component(calendar, ICAL_VJOURNAL_COMPONENT);
2817     while (c) {
2818         Journal::Ptr journal = readJournal(c, &timeZoneCache);
2819         if (journal) {
2820             Journal::Ptr old = cal->journal(journal->uid(), journal->recurrenceId());
2821             if (old) {
2822                 if (deleted) {
2823                     cal->deleteJournal(old); // move old to deleted
2824                 } else if (journal->revision() > old->revision()) {
2825                     cal->deleteJournal(old); // move old to deleted
2826                     cal->addJournal(journal); // and replace it with this one
2827                 }
2828             } else if (deleted) {
2829                 old = cal->deletedJournal(journal->uid(), journal->recurrenceId());
2830                 if (!old) {
2831                     cal->addJournal(journal); // add this one
2832                     cal->deleteJournal(journal); // and move it to deleted
2833                 }
2834             } else {
2835                 cal->addJournal(journal); // just add this one
2836             }
2837             if (!notebook.isEmpty() && cal->journal(journal->uid(), journal->recurrenceId())) {
2838                 cal->setNotebook(journal, notebook);
2839             }
2840         }
2841         c = icalcomponent_get_next_component(calendar, ICAL_VJOURNAL_COMPONENT);
2842     }
2843 
2844     // TODO: Remove any previous time zones no longer referenced in the calendar
2845 
2846     return true;
2847 }
2848 
2849 QString ICalFormatImpl::extractErrorProperty(icalcomponent *c)
2850 {
2851     QString errorMessage;
2852 
2853     icalproperty *error = icalcomponent_get_first_property(c, ICAL_XLICERROR_PROPERTY);
2854     while (error) {
2855         errorMessage += QLatin1String(icalproperty_get_xlicerror(error));
2856         errorMessage += QLatin1Char('\n');
2857         error = icalcomponent_get_next_property(c, ICAL_XLICERROR_PROPERTY);
2858     }
2859 
2860     return errorMessage;
2861 }
2862 
2863 /*
2864 void ICalFormatImpl::dumpIcalRecurrence( const icalrecurrencetype &r )
2865 {
2866   int i;
2867 
2868   qCDebug(KCALCORE_LOG) << " Freq:" << int( r.freq );
2869   qCDebug(KCALCORE_LOG) << " Until:" << icaltime_as_ical_string( r.until );
2870   qCDebug(KCALCORE_LOG) << " Count:" << r.count;
2871   if ( r.by_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
2872     int index = 0;
2873     QString out = " By Day: ";
2874     while ( ( i = r.by_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
2875       out.append( QString::number( i ) + ' ' );
2876     }
2877     qCDebug(KCALCORE_LOG) << out;
2878   }
2879   if ( r.by_month_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
2880     int index = 0;
2881     QString out = " By Month Day: ";
2882     while ( ( i = r.by_month_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
2883       out.append( QString::number( i ) + ' ' );
2884     }
2885     qCDebug(KCALCORE_LOG) << out;
2886   }
2887   if ( r.by_year_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
2888     int index = 0;
2889     QString out = " By Year Day: ";
2890     while ( ( i = r.by_year_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
2891       out.append( QString::number( i ) + ' ' );
2892     }
2893     qCDebug(KCALCORE_LOG) << out;
2894   }
2895   if ( r.by_month[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
2896     int index = 0;
2897     QString out = " By Month: ";
2898     while ( ( i = r.by_month[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
2899       out.append( QString::number( i ) + ' ' );
2900     }
2901     qCDebug(KCALCORE_LOG) << out;
2902   }
2903   if ( r.by_set_pos[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
2904     int index = 0;
2905     QString out = " By Set Pos: ";
2906     while ( ( i = r.by_set_pos[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
2907       qCDebug(KCALCORE_LOG) << "=========" << i;
2908       out.append( QString::number( i ) + ' ' );
2909     }
2910     qCDebug(KCALCORE_LOG) << out;
2911   }
2912 }
2913 */
2914 
2915 icalcomponent *ICalFormatImpl::createScheduleComponent(const IncidenceBase::Ptr &incidence, iTIPMethod method)
2916 {
2917     icalcomponent *message = createCalendarComponent();
2918 
2919     // Create VTIMEZONE components for this incidence
2920     TimeZoneList zones;
2921     if (incidence) {
2922         const QDateTime kd1 = incidence->dateTime(IncidenceBase::RoleStartTimeZone);
2923         const QDateTime kd2 = incidence->dateTime(IncidenceBase::RoleEndTimeZone);
2924 
2925         if (kd1.isValid() && kd1.timeZone() != QTimeZone::utc()) {
2926             zones << kd1.timeZone();
2927         }
2928 
2929         if (kd2.isValid() && kd2.timeZone() != QTimeZone::utc() && kd1.timeZone() != kd2.timeZone()) {
2930             zones << kd2.timeZone();
2931         }
2932 
2933         TimeZoneEarliestDate earliestTz;
2934         ICalTimeZoneParser::updateTzEarliestDate(incidence, &earliestTz);
2935 
2936         for (const auto &qtz : std::as_const(zones)) {
2937             icaltimezone *icaltz = ICalTimeZoneParser::icaltimezoneFromQTimeZone(qtz, earliestTz[qtz]);
2938             if (!icaltz) {
2939                 qCritical() << "bad time zone";
2940             } else {
2941                 icalcomponent *tz = icalcomponent_new_clone(icaltimezone_get_component(icaltz));
2942                 icalcomponent_add_component(message, tz);
2943                 icaltimezone_free(icaltz, 1);
2944             }
2945         }
2946     } else {
2947         qCDebug(KCALCORE_LOG) << "No incidence";
2948         return message;
2949     }
2950 
2951     icalproperty_method icalmethod = ICAL_METHOD_NONE;
2952 
2953     switch (method) {
2954     case iTIPPublish:
2955         icalmethod = ICAL_METHOD_PUBLISH;
2956         break;
2957     case iTIPRequest:
2958         icalmethod = ICAL_METHOD_REQUEST;
2959         break;
2960     case iTIPRefresh:
2961         icalmethod = ICAL_METHOD_REFRESH;
2962         break;
2963     case iTIPCancel:
2964         icalmethod = ICAL_METHOD_CANCEL;
2965         break;
2966     case iTIPAdd:
2967         icalmethod = ICAL_METHOD_ADD;
2968         break;
2969     case iTIPReply:
2970         icalmethod = ICAL_METHOD_REPLY;
2971         break;
2972     case iTIPCounter:
2973         icalmethod = ICAL_METHOD_COUNTER;
2974         break;
2975     case iTIPDeclineCounter:
2976         icalmethod = ICAL_METHOD_DECLINECOUNTER;
2977         break;
2978     default:
2979         qCDebug(KCALCORE_LOG) << "Unknown method";
2980         return message;
2981     }
2982 
2983     icalcomponent_add_property(message, icalproperty_new_method(icalmethod));
2984 
2985     icalcomponent *inc = writeIncidence(incidence, method);
2986 
2987     if (method != KCalendarCore::iTIPNoMethod) {
2988         // Not very nice, but since dtstamp changes semantics if used in scheduling, we have to adapt
2989         icalcomponent_set_dtstamp(inc, writeICalUtcDateTime(QDateTime::currentDateTimeUtc()));
2990     }
2991 
2992     /*
2993      * RFC 2446 states in section 3.4.3 ( REPLY to a VTODO ), that
2994      * a REQUEST-STATUS property has to be present. For the other two, event and
2995      * free busy, it can be there, but is optional. Until we do more
2996      * fine grained handling, assume all is well. Note that this is the
2997      * status of the _request_, not the attendee. Just to avoid confusion.
2998      * - till
2999      */
3000     if (icalmethod == ICAL_METHOD_REPLY) {
3001         struct icalreqstattype rst;
3002         rst.code = ICAL_2_0_SUCCESS_STATUS;
3003         rst.desc = nullptr;
3004         rst.debug = nullptr;
3005         icalcomponent_add_property(inc, icalproperty_new_requeststatus(rst));
3006     }
3007     icalcomponent_add_component(message, inc);
3008 
3009     return message;
3010 }