File indexing completed on 2024-12-22 04:56:56
0001 /* 0002 SPDX-FileCopyrightText: 2011 Grégory Oestreicher <greg@kamago.net> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "davfreebusyhandler.h" 0008 0009 #include "settings.h" 0010 #include <KDAV/DavCollectionsFetchJob> 0011 #include <KDAV/DavPrincipalSearchJob> 0012 #include <QDomElement> 0013 0014 #include "davresource_debug.h" 0015 #include <KCalendarCore/ICalFormat> 0016 #include <KIO/Job> 0017 #include <KIO/StoredTransferJob> 0018 #include <KLocalizedString> 0019 0020 static QDomElement firstChildElementNS(const QDomElement &parent, const QString &namespaceUri, const QString &tagName) 0021 { 0022 for (QDomNode child = parent.firstChild(); !child.isNull(); child = child.nextSibling()) { 0023 if (child.isElement()) { 0024 const QDomElement elt = child.toElement(); 0025 if (tagName.isEmpty() || (elt.tagName() == tagName && elt.namespaceURI() == namespaceUri)) { 0026 return elt; 0027 } 0028 } 0029 } 0030 0031 return {}; 0032 } 0033 0034 DavFreeBusyHandler::DavFreeBusyHandler(QObject *parent) 0035 : QObject(parent) 0036 { 0037 } 0038 0039 void DavFreeBusyHandler::canHandleFreeBusy(const QString &email) 0040 { 0041 const KDAV::DavUrl::List urls = Settings::self()->configuredDavUrls(); 0042 for (const KDAV::DavUrl &url : urls) { 0043 if (url.protocol() == KDAV::CalDav) { 0044 ++mRequestsTracker[email].handlingJobCount; 0045 auto job = new KDAV::DavPrincipalSearchJob(url, KDAV::DavPrincipalSearchJob::EmailAddress, email); 0046 job->setProperty("email", QVariant::fromValue(email)); 0047 job->setProperty("url", QVariant::fromValue(url.url().toString())); 0048 job->fetchProperty(QStringLiteral("schedule-inbox-URL"), QStringLiteral("urn:ietf:params:xml:ns:caldav")); 0049 connect(job, &KDAV::DavPrincipalSearchJob::result, this, &DavFreeBusyHandler::onPrincipalSearchJobFinished); 0050 job->start(); 0051 } 0052 } 0053 } 0054 0055 void DavFreeBusyHandler::retrieveFreeBusy(const QString &email, const QDateTime &start, const QDateTime &end) 0056 { 0057 if (!mPrincipalScheduleOutbox.contains(email)) { 0058 Q_EMIT freeBusyRetrieved(email, QString(), false, i18n("No schedule-outbox found for %1", email)); 0059 return; 0060 } 0061 0062 KCalendarCore::FreeBusy::Ptr fb(new KCalendarCore::FreeBusy(start, end)); 0063 KCalendarCore::Attendee att(QString(), email); 0064 fb->addAttendee(att); 0065 0066 KCalendarCore::ICalFormat formatter; 0067 const QByteArray fbData = formatter.createScheduleMessage(fb, KCalendarCore::iTIPRequest).toUtf8(); 0068 0069 const QStringList principalScheduleOutboxFromEmail = mPrincipalScheduleOutbox[email]; 0070 for (const QString &outbox : principalScheduleOutboxFromEmail) { 0071 ++mRequestsTracker[email].retrievalJobCount; 0072 const uint requestId = mNextRequestId++; 0073 0074 QUrl url(outbox); 0075 KIO::StoredTransferJob *job = KIO::storedHttpPost(fbData, url); 0076 job->addMetaData(QStringLiteral("content-type"), QStringLiteral("text/calendar")); 0077 job->setProperty("email", QVariant::fromValue(email)); 0078 job->setProperty("request-id", QVariant::fromValue(requestId)); 0079 connect(job, &KDAV::DavPrincipalSearchJob::result, this, &DavFreeBusyHandler::onRetrieveFreeBusyJobFinished); 0080 job->start(); 0081 } 0082 } 0083 0084 void DavFreeBusyHandler::onPrincipalSearchJobFinished(KJob *job) 0085 { 0086 const QString email = job->property("email").toString(); 0087 int handlingJobCount = --mRequestsTracker[email].handlingJobCount; 0088 0089 if (job->error()) { 0090 if (handlingJobCount == 0 && !mRequestsTracker[email].handlingJobSuccessful) { 0091 Q_EMIT handlesFreeBusy(email, false); 0092 } 0093 return; 0094 } 0095 0096 auto davJob = qobject_cast<KDAV::DavPrincipalSearchJob *>(job); 0097 const QList<KDAV::DavPrincipalSearchJob::Result> results = davJob->results(); 0098 0099 if (results.isEmpty()) { 0100 if (handlingJobCount == 0 && !mRequestsTracker[email].handlingJobSuccessful) { 0101 Q_EMIT handlesFreeBusy(email, false); 0102 } 0103 return; 0104 } 0105 0106 mRequestsTracker[email].handlingJobSuccessful = true; 0107 0108 for (const KDAV::DavPrincipalSearchJob::Result &result : std::as_const(results)) { 0109 qCDebug(DAVRESOURCE_LOG) << result.value; 0110 QUrl url(davJob->property("url").toString()); 0111 if (result.value.startsWith(QLatin1Char('/'))) { 0112 // href is only a path, use request url to complete 0113 url.setPath(result.value, QUrl::TolerantMode); 0114 } else { 0115 // href is a complete url 0116 url = QUrl::fromUserInput(result.value); 0117 } 0118 0119 if (!mPrincipalScheduleOutbox[email].contains(url.url())) { 0120 mPrincipalScheduleOutbox[email] << url.url(); 0121 } 0122 } 0123 0124 if (handlingJobCount == 0) { 0125 Q_EMIT handlesFreeBusy(email, true); 0126 } 0127 } 0128 0129 void DavFreeBusyHandler::onRetrieveFreeBusyJobFinished(KJob *job) 0130 { 0131 const QString email = job->property("email").toString(); 0132 uint requestId = job->property("request-id").toUInt(); 0133 int retrievalJobCount = --mRequestsTracker[email].retrievalJobCount; 0134 0135 if (job->error()) { 0136 if (retrievalJobCount == 0 && !mRequestsTracker[email].retrievalJobSuccessful) { 0137 Q_EMIT freeBusyRetrieved(email, QString(), false, job->errorString()); 0138 } 0139 return; 0140 } 0141 0142 /* 0143 * Extract info from a document like the following: 0144 * <?xml version="1.0" encoding="utf-8" ?> 0145 * <C:schedule-response xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav"> 0146 * <C:response> 0147 * <C:recipient> 0148 * <D:href>mailto:wilfredo@example.com<D:href> 0149 * </C:recipient> 0150 * <C:request-status>2.0;Success</C:request-status> 0151 * <C:calendar-data>BEGIN:VCALENDAR 0152 * VERSION:2.0 0153 * PRODID:-//Example Corp.//CalDAV Server//EN 0154 * METHOD:REPLY 0155 * BEGIN:VFREEBUSY 0156 * UID:4FD3AD926350 0157 * DTSTAMP:20090602T200733Z 0158 * DTSTART:20090602T000000Z 0159 * DTEND:20090604T000000Z 0160 * ORGANIZER;CN="Cyrus Daboo":mailto:cyrus@example.com 0161 * ATTENDEE;CN="Wilfredo Sanchez Vega":mailto:wilfredo@example.com 0162 * FREEBUSY;FBTYPE=BUSY:20090602T110000Z/20090602T120000Z 0163 * FREEBUSY;FBTYPE=BUSY:20090603T170000Z/20090603T180000Z 0164 * END:VFREEBUSY 0165 * END:VCALENDAR 0166 * </C:calendar-data> 0167 * </C:response> 0168 * <C:response> 0169 * <C:recipient> 0170 * <D:href>mailto:mike@example.org<D:href> 0171 * </C:recipient> 0172 * <C:request-status>3.7;Invalid calendar user</C:request-status> 0173 * </C:response> 0174 * </C:schedule-response> 0175 */ 0176 0177 auto postJob = qobject_cast<KIO::StoredTransferJob *>(job); 0178 QDomDocument response; 0179 response.setContent(postJob->data(), true); 0180 0181 QDomElement scheduleResponse = response.documentElement(); 0182 0183 // We are only expecting one response tag 0184 QDomElement responseElement = firstChildElementNS(scheduleResponse, QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("response")); 0185 if (responseElement.isNull()) { 0186 if (retrievalJobCount == 0 && !mRequestsTracker[email].retrievalJobSuccessful) { 0187 Q_EMIT freeBusyRetrieved(email, QString(), false, i18n("Invalid response from the server")); 0188 } 0189 return; 0190 } 0191 0192 // We can load directly the calendar-data and use its content to create 0193 // an incidence base that will give us everything we need to test 0194 // the success 0195 QDomElement calendarDataElement = firstChildElementNS(responseElement, QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-data")); 0196 if (calendarDataElement.isNull()) { 0197 if (retrievalJobCount == 0 && !mRequestsTracker[email].retrievalJobSuccessful) { 0198 Q_EMIT freeBusyRetrieved(email, QString(), false, i18n("Invalid response from the server")); 0199 } 0200 return; 0201 } 0202 0203 const QString rawData = calendarDataElement.text(); 0204 0205 KCalendarCore::ICalFormat format; 0206 KCalendarCore::FreeBusy::Ptr fb = format.parseFreeBusy(rawData); 0207 if (fb.isNull()) { 0208 if (retrievalJobCount == 0 && !mRequestsTracker[email].retrievalJobSuccessful) { 0209 Q_EMIT freeBusyRetrieved(email, QString(), false, i18n("Unable to parse free-busy data received")); 0210 } 0211 return; 0212 } 0213 0214 // We're safe now 0215 mRequestsTracker[email].retrievalJobSuccessful = true; 0216 0217 // fb->clearAttendees(); 0218 if (mRequestsTracker[email].resultingFreeBusy[requestId].isNull()) { 0219 mRequestsTracker[email].resultingFreeBusy[requestId] = fb; 0220 } else { 0221 mRequestsTracker[email].resultingFreeBusy[requestId]->merge(fb); 0222 } 0223 0224 if (retrievalJobCount == 0) { 0225 const QString fbStr = format.createScheduleMessage(mRequestsTracker[email].resultingFreeBusy[requestId], KCalendarCore::iTIPRequest); 0226 Q_EMIT freeBusyRetrieved(email, fbStr, true, QString()); 0227 } 0228 } 0229 0230 #include "moc_davfreebusyhandler.cpp"