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