File indexing completed on 2024-12-22 04:57:03

0001 /*
0002     SPDX-FileCopyrightText: 2015-2017 Krzysztof Nowicki <krissn@op.pl>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "ewsrecurrence.h"
0008 
0009 #include <QBitArray>
0010 #include <QDate>
0011 #include <QXmlStreamReader>
0012 
0013 #include "ewsclient_debug.h"
0014 #include "ewstypes.h"
0015 
0016 using namespace KCalendarCore;
0017 
0018 static const QString dayOfWeekNames[] = {
0019     QStringLiteral("Monday"),
0020     QStringLiteral("Tuesday"),
0021     QStringLiteral("Wednesday"),
0022     QStringLiteral("Thursday"),
0023     QStringLiteral("Friday"),
0024     QStringLiteral("Saturday"),
0025     QStringLiteral("Sunday"),
0026     QStringLiteral("Day"),
0027     QStringLiteral("Weekday"),
0028     QStringLiteral("WeekendDay"),
0029 };
0030 constexpr unsigned dayOfWeekNameCount = sizeof(dayOfWeekNames) / sizeof(dayOfWeekNames[0]);
0031 
0032 static const QString dayOfWeekIndexNames[] = {
0033     QStringLiteral("First"),
0034     QStringLiteral("Second"),
0035     QStringLiteral("Third"),
0036     QStringLiteral("Fourth"),
0037     QStringLiteral("Last"),
0038 };
0039 constexpr unsigned dayOfWeekIndexNameCount = sizeof(dayOfWeekIndexNames) / sizeof(dayOfWeekIndexNames[0]);
0040 
0041 static const QString monthNames[] = {
0042     QStringLiteral("January"),
0043     QStringLiteral("February"),
0044     QStringLiteral("March"),
0045     QStringLiteral("April"),
0046     QStringLiteral("May"),
0047     QStringLiteral("June"),
0048     QStringLiteral("July"),
0049     QStringLiteral("August"),
0050     QStringLiteral("September"),
0051     QStringLiteral("October"),
0052     QStringLiteral("November"),
0053     QStringLiteral("December"),
0054 };
0055 constexpr unsigned monthNameCount = sizeof(monthNames) / sizeof(monthNames[0]);
0056 
0057 EwsRecurrence::EwsRecurrence()
0058     : Recurrence()
0059 {
0060 }
0061 
0062 EwsRecurrence::EwsRecurrence(QXmlStreamReader &reader)
0063 {
0064     while (reader.readNextStartElement()) {
0065         QString elmName = reader.name().toString();
0066         if (reader.namespaceUri() != ewsTypeNsUri) {
0067             qCWarningNC(EWSCLI_LOG) << QStringLiteral("Unexpected namespace in %1 element:").arg(elmName) << reader.namespaceUri();
0068             return;
0069         }
0070 
0071         if (elmName == QLatin1StringView("RelativeYearlyRecurrence")) {
0072             if (!readRelativeYearlyRecurrence(reader)) {
0073                 return;
0074             }
0075         } else if (elmName == QLatin1StringView("AbsoluteYearlyRecurrence")) {
0076             if (!readAbsoluteYearlyRecurrence(reader)) {
0077                 return;
0078             }
0079         } else if (elmName == QLatin1StringView("RelativeMonthlyRecurrence")) {
0080             if (!readRelativeMonthlyRecurrence(reader)) {
0081                 return;
0082             }
0083         } else if (elmName == QLatin1StringView("AbsoluteMonthlyRecurrence")) {
0084             if (!readAbsoluteMonthlyRecurrence(reader)) {
0085                 return;
0086             }
0087         } else if (elmName == QLatin1StringView("WeeklyRecurrence")) {
0088             if (!readWeeklyRecurrence(reader)) {
0089                 return;
0090             }
0091         } else if (elmName == QLatin1StringView("DailyRecurrence")) {
0092             if (!readWeeklyRecurrence(reader)) {
0093                 return;
0094             }
0095         } else if (elmName == QLatin1StringView("NoEndRecurrence")) {
0096             // Ignore - this is the default
0097             reader.skipCurrentElement();
0098         } else if (elmName == QLatin1StringView("EndDateRecurrence")) {
0099             if (!readEndDateRecurrence(reader)) {
0100                 return;
0101             }
0102         } else if (elmName == QLatin1StringView("NumberedRecurrence")) {
0103             if (!readNumberedRecurrence(reader)) {
0104                 return;
0105             }
0106         } else {
0107             qCWarningNC(EWSCLI_LOG) << QStringLiteral("Failed to read %1 element - unknown element: %2.").arg(QStringLiteral("Recurrence"), elmName);
0108             return;
0109         }
0110     }
0111 }
0112 
0113 EwsRecurrence::EwsRecurrence(const EwsRecurrence &other)
0114     : Recurrence(other)
0115 {
0116 }
0117 
0118 bool EwsRecurrence::readRelativeYearlyRecurrence(QXmlStreamReader &reader)
0119 {
0120     QBitArray dow(7);
0121     short dowWeekIndex = 0;
0122     short month = 0;
0123     bool hasDow = false;
0124     bool hasDowWeekIndex = false;
0125     bool hasMonth = false;
0126 
0127     while (reader.readNextStartElement()) {
0128         QString elmName = reader.name().toString();
0129         if (reader.namespaceUri() != ewsTypeNsUri) {
0130             qCWarningNC(EWSCLI_LOG) << QStringLiteral("Unexpected namespace in %1 element:").arg(elmName) << reader.namespaceUri();
0131             return false;
0132         }
0133 
0134         if (elmName == QLatin1StringView("DaysOfWeek")) {
0135             if (!readDow(reader, dow)) {
0136                 return false;
0137             }
0138             hasDow = true;
0139         } else if (elmName == QLatin1StringView("DayOfWeekIndex")) {
0140             bool ok;
0141             QString text = reader.readElementText();
0142             dowWeekIndex = decodeEnumString<short>(text, dayOfWeekIndexNames, dayOfWeekIndexNameCount, &ok);
0143             if (reader.error() != QXmlStreamReader::NoError || !ok) {
0144                 qCWarning(EWSCLI_LOG)
0145                     << QStringLiteral("Failed to read EWS request - invalid %1 element (value: %2).").arg(QStringLiteral("DayOfWeekIndex").arg(text));
0146                 return false;
0147             }
0148             if (dowWeekIndex == 4) { // "Last"
0149                 dowWeekIndex = -1;
0150             }
0151             hasDowWeekIndex = true;
0152         } else if (elmName == QLatin1StringView("Month")) {
0153             bool ok;
0154             QString text = reader.readElementText();
0155             month = decodeEnumString<short>(text, monthNames, monthNameCount, &ok);
0156             if (reader.error() != QXmlStreamReader::NoError || !ok) {
0157                 qCWarning(EWSCLI_LOG) << QStringLiteral("Failed to read EWS request - invalid %1 element (value: %2).").arg(QStringLiteral("Month"), text);
0158                 return false;
0159             }
0160             hasMonth = true;
0161         } else {
0162             qCWarning(EWSCLI_LOG)
0163                 << QStringLiteral("Failed to read %1 element - unknown element: %2.").arg(QStringLiteral("RelativeYearlyRecurrence"), elmName);
0164             return false;
0165         }
0166     }
0167 
0168     if (!hasMonth || !hasDow || !hasDowWeekIndex) {
0169         qCWarningNC(EWSCLI_LOG) << QStringLiteral("Failed to read Recurrence element - expected all of Month, DaysOfWeek and DayOfWeekIndex elements.");
0170         return false;
0171     }
0172 
0173     addYearlyMonth(month);
0174     addYearlyPos(dowWeekIndex, dow);
0175 
0176     return true;
0177 }
0178 
0179 bool EwsRecurrence::readAbsoluteYearlyRecurrence(QXmlStreamReader &reader)
0180 {
0181     short dom = 0;
0182     short month = 0;
0183     bool hasDom = false;
0184     bool hasMonth = false;
0185 
0186     while (reader.readNextStartElement()) {
0187         QString elmName = reader.name().toString();
0188         if (reader.namespaceUri() != ewsTypeNsUri) {
0189             qCWarningNC(EWSCLI_LOG) << QStringLiteral("Unexpected namespace in %1 element:").arg(elmName) << reader.namespaceUri();
0190             return false;
0191         }
0192 
0193         if (elmName == QLatin1StringView("DayOfMonth")) {
0194             bool ok;
0195             QString text = reader.readElementText();
0196             dom = text.toShort(&ok);
0197             if (reader.error() != QXmlStreamReader::NoError || !ok) {
0198                 qCWarning(EWSCLI_LOG)
0199                     << QStringLiteral("Failed to read EWS request - invalid %1 element (value: %2).").arg(QStringLiteral("DayOfMonth").arg(text));
0200                 return false;
0201             }
0202             hasDom = true;
0203         } else if (elmName == QLatin1StringView("Month")) {
0204             bool ok;
0205             QString text = reader.readElementText();
0206             month = decodeEnumString<short>(text, monthNames, monthNameCount, &ok);
0207             if (reader.error() != QXmlStreamReader::NoError || !ok) {
0208                 qCWarning(EWSCLI_LOG) << QStringLiteral("Failed to read EWS request - invalid %1 element (value: %2).").arg(QStringLiteral("Month").arg(text));
0209                 return false;
0210             }
0211             hasMonth = true;
0212         } else {
0213             qCWarning(EWSCLI_LOG) << QStringLiteral("Failed to read recurrence element - unknown element: %1.").arg(elmName);
0214             return false;
0215         }
0216     }
0217 
0218     if (!hasDom || !hasMonth) {
0219         qCWarning(EWSCLI_LOG) << QStringLiteral("Failed to read recurrence element - need both month and dom values.");
0220         return false;
0221     }
0222 
0223     // "If for a particular month this value is larger than the number of days in the month,
0224     //  the last day of the month is assumed for this property."
0225     QDate date(2000, month, 1);
0226     if (dom > date.daysInMonth()) {
0227         dom = -1;
0228     }
0229 
0230     addYearlyMonth(month);
0231     addYearlyDay(dom);
0232 
0233     return true;
0234 }
0235 
0236 bool EwsRecurrence::readRelativeMonthlyRecurrence(QXmlStreamReader &reader)
0237 {
0238     QBitArray dow(7);
0239     short dowWeekIndex = 0;
0240     int interval = 0;
0241     bool hasDow = false;
0242     bool hasDowWeekIndex = false;
0243     bool hasInterval = false;
0244 
0245     while (reader.readNextStartElement()) {
0246         QString elmName = reader.name().toString();
0247         if (reader.namespaceUri() != ewsTypeNsUri) {
0248             qCWarningNC(EWSCLI_LOG) << QStringLiteral("Unexpected namespace in %1 element:").arg(elmName) << reader.namespaceUri();
0249             return false;
0250         }
0251 
0252         if (elmName == QLatin1StringView("Interval")) {
0253             bool ok;
0254             QString text = reader.readElementText();
0255             interval = text.toInt(&ok);
0256             if (reader.error() != QXmlStreamReader::NoError || !ok) {
0257                 qCWarning(EWSCLI_LOG)
0258                     << QStringLiteral("Failed to read EWS request - invalid %1 element (value: %2).").arg(QStringLiteral("Interval").arg(text));
0259                 return false;
0260             }
0261             hasInterval = true;
0262         } else if (elmName == QLatin1StringView("DaysOfWeek")) {
0263             if (!readDow(reader, dow)) {
0264                 return false;
0265             }
0266             hasDow = true;
0267         } else if (elmName == QLatin1StringView("DayOfWeekIndex")) {
0268             bool ok;
0269             QString text = reader.readElementText();
0270             dowWeekIndex = decodeEnumString<short>(text, dayOfWeekIndexNames, dayOfWeekIndexNameCount, &ok);
0271             if (reader.error() != QXmlStreamReader::NoError || !ok) {
0272                 qCWarning(EWSCLI_LOG)
0273                     << QStringLiteral("Failed to read EWS request - invalid %1 element (value: %2).").arg(QStringLiteral("DayOfWeekIndex").arg(text));
0274                 return false;
0275             }
0276             if (dowWeekIndex == 4) { // "Last"
0277                 dowWeekIndex = -1;
0278             }
0279             hasDowWeekIndex = true;
0280         } else {
0281             qCWarning(EWSCLI_LOG) << QStringLiteral("Failed to read Recurrence element - unknown element: %1.").arg(elmName);
0282             return false;
0283         }
0284     }
0285 
0286     if (!hasInterval || !hasDow || !hasDowWeekIndex) {
0287         qCWarningNC(EWSCLI_LOG) << QStringLiteral("Failed to read Recurrence element - expected all of Interval, DaysOfWeek and DayOfWeekIndex elements.");
0288         return false;
0289     }
0290 
0291     addMonthlyPos(dowWeekIndex, dow);
0292     setFrequency(interval);
0293 
0294     return true;
0295 }
0296 
0297 bool EwsRecurrence::readAbsoluteMonthlyRecurrence(QXmlStreamReader &reader)
0298 {
0299     short dom = 0;
0300     int interval = 0;
0301     bool hasInterval = false;
0302     bool hasDom = false;
0303 
0304     while (reader.readNextStartElement()) {
0305         QString elmName = reader.name().toString();
0306         if (reader.namespaceUri() != ewsTypeNsUri) {
0307             qCWarningNC(EWSCLI_LOG) << QStringLiteral("Unexpected namespace in %1 element:").arg(elmName) << reader.namespaceUri();
0308             return false;
0309         }
0310 
0311         if (elmName == QLatin1StringView("Interval")) {
0312             bool ok;
0313             QString text = reader.readElementText();
0314             interval = text.toInt(&ok);
0315             if (reader.error() != QXmlStreamReader::NoError || !ok) {
0316                 qCWarningNC(EWSCLI_LOG)
0317                     << QStringLiteral("Failed to read EWS request - invalid %1 element (value: %2).").arg(QStringLiteral("Interval").arg(text));
0318                 return false;
0319             }
0320             hasInterval = true;
0321         } else if (elmName == QLatin1StringView("DayOfMonth")) {
0322             bool ok;
0323             QString text = reader.readElementText();
0324             dom = text.toShort(&ok);
0325             if (reader.error() != QXmlStreamReader::NoError || !ok) {
0326                 qCWarningNC(EWSCLI_LOG)
0327                     << QStringLiteral("Failed to read EWS request - invalid %1 element (value: %2).").arg(QStringLiteral("DayOfMonth").arg(text));
0328                 return false;
0329             }
0330             hasDom = true;
0331         } else {
0332             qCWarningNC(EWSCLI_LOG) << QStringLiteral("Failed to read Recurrence element - unknown element: %1.").arg(elmName);
0333             return false;
0334         }
0335     }
0336 
0337     if (!hasInterval || !hasDom) {
0338         qCWarningNC(EWSCLI_LOG) << QStringLiteral("Failed to read Recurrence element - expected both Interval and DayOfMonth.");
0339         return false;
0340     }
0341 
0342     addMonthlyDate(dom);
0343     setFrequency(interval);
0344 
0345     return true;
0346 }
0347 
0348 bool EwsRecurrence::readWeeklyRecurrence(QXmlStreamReader &reader)
0349 {
0350     int interval = 1;
0351     QBitArray dow(7);
0352     int weekStart = 0;
0353     bool hasInterval = false;
0354     bool hasDow = false;
0355     bool hasWeekStart = false;
0356 
0357     while (reader.readNextStartElement()) {
0358         QString elmName = reader.name().toString();
0359         if (reader.namespaceUri() != ewsTypeNsUri) {
0360             qCWarningNC(EWSCLI_LOG) << QStringLiteral("Unexpected namespace in %1 element:").arg(elmName) << reader.namespaceUri();
0361             return false;
0362         }
0363 
0364         if (elmName == QLatin1StringView("Interval")) {
0365             bool ok;
0366             QString text = reader.readElementText();
0367             interval = text.toInt(&ok);
0368             if (reader.error() != QXmlStreamReader::NoError || !ok) {
0369                 qCWarningNC(EWSCLI_LOG)
0370                     << QStringLiteral("Failed to read EWS request - invalid %1 element (value: %2).").arg(QStringLiteral("Interval").arg(text));
0371                 return false;
0372             }
0373             hasInterval = true;
0374         } else if (elmName == QLatin1StringView("DaysOfWeek")) {
0375             if (!readDow(reader, dow)) {
0376                 return false;
0377             }
0378             hasDow = true;
0379         } else if (elmName == QLatin1StringView("FirstDayOfWeek")) {
0380             bool ok;
0381             QString text = reader.readElementText();
0382             weekStart = decodeEnumString<int>(text, dayOfWeekNames, dayOfWeekNameCount, &ok) + 1;
0383             if (reader.error() != QXmlStreamReader::NoError || !ok) {
0384                 qCWarningNC(EWSCLI_LOG)
0385                     << QStringLiteral("Failed to read EWS request - invalid %1 element (value: %2).").arg(QStringLiteral("FirstDayOfWeek").arg(text));
0386                 return false;
0387             }
0388             hasWeekStart = true;
0389         } else {
0390             qCWarningNC(EWSCLI_LOG) << QStringLiteral("Failed to read %1 element - unknown element: %2.").arg(QStringLiteral("WeeklyRecurrence"), elmName);
0391             return false;
0392         }
0393     }
0394 
0395     if (!hasInterval || !hasDow || !hasWeekStart) {
0396         qCWarningNC(EWSCLI_LOG) << QStringLiteral("Failed to read Recurrence element - expected all of Interval, DaysOfWeek and FirstDatOfWeek elements.");
0397         return false;
0398     }
0399 
0400     setWeekly(interval, dow, weekStart);
0401 
0402     return true;
0403 }
0404 
0405 bool EwsRecurrence::readDailyRecurrence(QXmlStreamReader &reader)
0406 {
0407     int interval = 1;
0408     bool hasInterval = false;
0409 
0410     while (reader.readNextStartElement()) {
0411         QString elmName = reader.name().toString();
0412         if (reader.namespaceUri() != ewsTypeNsUri) {
0413             qCWarningNC(EWSCLI_LOG) << QStringLiteral("Unexpected namespace in %1 element:").arg(elmName) << reader.namespaceUri();
0414             return false;
0415         }
0416 
0417         if (elmName == QLatin1StringView("Interval")) {
0418             bool ok;
0419             QString text = reader.readElementText();
0420             interval = text.toInt(&ok);
0421             if (reader.error() != QXmlStreamReader::NoError || !ok) {
0422                 qCWarning(EWSCLI_LOG)
0423                     << QStringLiteral("Failed to read EWS request - invalid %1 element (value: %2).").arg(QStringLiteral("Interval").arg(text));
0424                 return false;
0425             }
0426             hasInterval = true;
0427         } else {
0428             qCWarning(EWSCLI_LOG) << QStringLiteral("Failed to read recurrence element - unknown element: %1.").arg(elmName);
0429             return false;
0430         }
0431     }
0432 
0433     if (!hasInterval) {
0434         qCWarningNC(EWSCLI_LOG) << QStringLiteral("Failed to read Recurrence element - expected an Interval element.");
0435         return false;
0436     }
0437 
0438     setDaily(interval);
0439 
0440     return true;
0441 }
0442 
0443 bool EwsRecurrence::readEndDateRecurrence(QXmlStreamReader &reader)
0444 {
0445     QDate dateEnd;
0446 
0447     while (reader.readNextStartElement()) {
0448         QString elmName = reader.name().toString();
0449         if (reader.namespaceUri() != ewsTypeNsUri) {
0450             qCWarningNC(EWSCLI_LOG) << QStringLiteral("Unexpected namespace in %1 element:").arg(elmName) << reader.namespaceUri();
0451             return false;
0452         }
0453 
0454         if (elmName == QLatin1StringView("EndDate")) {
0455             QString text = reader.readElementText();
0456             dateEnd = QDate::fromString(text, Qt::ISODate);
0457             if (reader.error() != QXmlStreamReader::NoError || !dateEnd.isValid()) {
0458                 qCWarning(EWSCLI_LOG)
0459                     << QStringLiteral("Failed to read EWS request - invalid %1 element (value: %2).").arg(QStringLiteral("EndDate").arg(text));
0460                 return false;
0461             }
0462         } else if (elmName == QLatin1StringView("StartDate")) {
0463             // Don't care
0464             reader.skipCurrentElement();
0465         } else {
0466             qCWarningNC(EWSCLI_LOG) << QStringLiteral("Failed to read %1 element - unknown element: %2.").arg(QStringLiteral("EndDateRecurrence"), elmName);
0467             return false;
0468         }
0469     }
0470 
0471     setEndDate(dateEnd);
0472 
0473     return true;
0474 }
0475 
0476 bool EwsRecurrence::readNumberedRecurrence(QXmlStreamReader &reader)
0477 {
0478     int numOccurrences = 0;
0479 
0480     while (reader.readNextStartElement()) {
0481         QString elmName = reader.name().toString();
0482         if (reader.namespaceUri() != ewsTypeNsUri) {
0483             qCWarningNC(EWSCLI_LOG) << QStringLiteral("Unexpected namespace in %1 element:").arg(elmName) << reader.namespaceUri();
0484             return false;
0485         }
0486 
0487         if (elmName == QLatin1StringView("NumberOfOccurrences")) {
0488             bool ok;
0489             QString text = reader.readElementText();
0490             numOccurrences = text.toInt(&ok);
0491             if (reader.error() != QXmlStreamReader::NoError || !ok) {
0492                 qCWarning(EWSCLI_LOG)
0493                     << QStringLiteral("Failed to read EWS request - invalid %1 element (value: %2).").arg(QStringLiteral("NumberOfOccurrences").arg(text));
0494                 return false;
0495             }
0496         } else if (elmName == QLatin1StringView("StartDate")) {
0497             // Don't care
0498             reader.skipCurrentElement();
0499         } else {
0500             qCWarning(EWSCLI_LOG) << QStringLiteral("Failed to read recurrence element - unknown element: %1.").arg(elmName);
0501             return false;
0502         }
0503     }
0504 
0505     setDuration(numOccurrences);
0506 
0507     return true;
0508 }
0509 
0510 bool EwsRecurrence::readDow(QXmlStreamReader &reader, QBitArray &dow)
0511 {
0512     bool ok;
0513     QString text = reader.readElementText();
0514     const QStringList days = text.split(QLatin1Char(' '));
0515     for (const QString &day : days) {
0516         auto dowIndex = decodeEnumString<short>(day, dayOfWeekNames, dayOfWeekNameCount, &ok);
0517         if (reader.error() != QXmlStreamReader::NoError || !ok) {
0518             qCWarning(EWSCLI_LOG) << QStringLiteral("Failed to read EWS request - invalid %1 element (value: %2).").arg(QStringLiteral("DaysOfWeek").arg(day));
0519             return false;
0520         }
0521         if (dowIndex == 7) { // "Day"
0522             dow.fill(true, 0, 7);
0523         } else if (dowIndex == 8) { // "Weekday"
0524             dow.fill(true, 0, 5);
0525         } else if (dowIndex == 9) { // "WeekendDay"
0526             dow.fill(true, 5, 7);
0527         } else {
0528             dow.setBit(dowIndex);
0529         }
0530     }
0531     return true;
0532 }