diff --git a/src/common/davitemsfetchjob.cpp b/src/common/davitemsfetchjob.cpp index 7fb9fed..fd13962 100644 --- a/src/common/davitemsfetchjob.cpp +++ b/src/common/davitemsfetchjob.cpp @@ -1,160 +1,159 @@ /* Copyright (c) 2010 Grégory Oestreicher Based on DavItemsListJob which is copyright (c) 2010 Tobias Koenig This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "davitemsfetchjob.h" #include "davmanager.h" #include "davmultigetprotocol.h" #include "utils.h" #include "daverror.h" #include #include using namespace KDAV; DavItemsFetchJob::DavItemsFetchJob(const DavUrl &collectionUrl, const QStringList &urls, QObject *parent) : DavJobBase(parent), mCollectionUrl(collectionUrl), mUrls(urls) { } void DavItemsFetchJob::start() { const DavMultigetProtocol *protocol = dynamic_cast(DavManager::self()->davProtocol(mCollectionUrl.protocol())); if (!protocol) { setError(ERR_NO_MULTIGET); setErrorTextFromDavError(); emitResult(); return; } const QDomDocument report = protocol->itemsReportQuery(mUrls)->buildQuery(); KIO::DavJob *job = DavManager::self()->createReportJob(mCollectionUrl.url(), report, QStringLiteral("0")); job->addMetaData(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true")); connect(job, &KIO::DavJob::result, this, &DavItemsFetchJob::davJobFinished); } DavItem::List DavItemsFetchJob::items() const { DavItem::List values; values.reserve(mItems.size()); Q_FOREACH (const auto &value, mItems) { values << value; } return values; } DavItem DavItemsFetchJob::item(const QString &url) const { return mItems.value(url); } void DavItemsFetchJob::davJobFinished(KJob *job) { KIO::DavJob *davJob = qobject_cast(job); const int responseCode = davJob->queryMetaData(QStringLiteral("responsecode")).isEmpty() ? 0 : davJob->queryMetaData(QStringLiteral("responsecode")).toInt(); // KIO::DavJob does not set error() even if the HTTP status code is a 4xx or a 5xx if (davJob->error() || (responseCode >= 400 && responseCode < 600)) { setLatestResponseCode(responseCode); setError(ERR_PROBLEM_WITH_REQUEST); setJobErrorText(davJob->errorText()); setJobError(davJob->error()); setErrorTextFromDavError(); emitResult(); return; } const DavMultigetProtocol *protocol = static_cast(DavManager::self()->davProtocol(mCollectionUrl.protocol())); const QDomDocument document = davJob->response(); const QDomElement documentElement = document.documentElement(); QDomElement responseElement = Utils::firstChildElementNS(documentElement, QStringLiteral("DAV:"), QStringLiteral("response")); while (!responseElement.isNull()) { QDomElement propstatElement = Utils::firstChildElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("propstat")); if (propstatElement.isNull()) { responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); continue; } // Check for errors const QDomElement statusElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("status")); if (!statusElement.text().contains(QLatin1String("200"))) { responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); continue; } const QDomElement propElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop")); DavItem item; // extract path const QDomElement hrefElement = Utils::firstChildElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("href")); const QString href = hrefElement.text(); - QUrl url = QUrl::fromUserInput(href); + QUrl url = davJob->url(); if (href.startsWith(QLatin1Char('/'))) { - // href is a relative URL (i.e. missing scheme, hostname and so on) - // QUrl would treat this as a file URL be default, avoid that by setting - // empty scheme. - // Note: We don't want to expand this into a full URL (including hostname) - // as some CalDav implementations don't seem to handle that correctly. - url.setScheme(QString()); + // href is only a path, use request url to complete + url.setPath(href, QUrl::TolerantMode); + } else { + // href is a complete url + url = QUrl::fromUserInput(href); } auto _url = url; _url.setUserInfo(mCollectionUrl.url().userInfo()); item.setUrl(DavUrl(_url, mCollectionUrl.protocol())); // extract etag const QDomElement getetagElement = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("getetag")); item.setEtag(getetagElement.text()); // extract content const QDomElement dataElement = Utils::firstChildElementNS(propElement, protocol->responseNamespace(), protocol->dataTagName()); if (dataElement.isNull()) { responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); continue; } const QByteArray data = dataElement.firstChild().toText().data().toUtf8(); if (data.isEmpty()) { responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); continue; } item.setData(data); mItems.insert(item.url().toDisplayString(), item); responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); } emitResult(); } diff --git a/src/common/davitemslistjob.cpp b/src/common/davitemslistjob.cpp index 8940668..4e178fd 100644 --- a/src/common/davitemslistjob.cpp +++ b/src/common/davitemslistjob.cpp @@ -1,268 +1,267 @@ /* Copyright (c) 2010 Tobias Koenig This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "davitemslistjob.h" #include "daverror.h" #include "davmanager.h" #include "davprotocolbase.h" #include "davurl.h" #include "utils.h" #include "etagcache.h" #include #include using namespace KDAV; class DavItemsListJobPrivate { public: DavItemsListJobPrivate(const DavUrl &url, const std::shared_ptr &cache); DavUrl mUrl; std::shared_ptr mEtagCache; QStringList mMimeTypes; QString mRangeStart; QString mRangeEnd; DavItem::List mItems; QSet mSeenUrls; // to prevent events duplication with some servers DavItem::List mChangedItems; QStringList mDeletedItems; uint mSubJobCount; }; DavItemsListJobPrivate::DavItemsListJobPrivate(const DavUrl &url, const std::shared_ptr &cache) : mUrl(url) , mEtagCache(cache) , mSubJobCount(0) { } DavItemsListJob::DavItemsListJob(const DavUrl &url, const std::shared_ptr &cache, QObject *parent) : DavJobBase(parent) , d(std::unique_ptr(new DavItemsListJobPrivate(url, cache))) { } DavItemsListJob::~DavItemsListJob() { } void DavItemsListJob::setContentMimeTypes(const QStringList &types) { d->mMimeTypes = types; } void DavItemsListJob::setTimeRange(const QString &start, const QString &end) { d->mRangeStart = start; d->mRangeEnd = end; } void DavItemsListJob::start() { const DavProtocolBase *protocol = DavManager::self()->davProtocol(d->mUrl.protocol()); Q_ASSERT(protocol); QVectorIterator it(protocol->itemsQueries()); while (it.hasNext()) { XMLQueryBuilder::Ptr builder = it.next(); if (!d->mRangeStart.isEmpty()) { builder->setParameter(QStringLiteral("start"), d->mRangeStart); } if (!d->mRangeEnd.isEmpty()) { builder->setParameter(QStringLiteral("end"), d->mRangeEnd); } const QDomDocument props = builder->buildQuery(); const QString mimeType = builder->mimeType(); if (d->mMimeTypes.isEmpty() || d->mMimeTypes.contains(mimeType)) { ++d->mSubJobCount; if (protocol->useReport()) { KIO::DavJob *job = DavManager::self()->createReportJob(d->mUrl.url(), props); job->addMetaData(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true")); job->setProperty("davType", QStringLiteral("report")); job->setProperty("itemsMimeType", mimeType); connect(job, &KIO::DavJob::result, this, &DavItemsListJob::davJobFinished); } else { KIO::DavJob *job = DavManager::self()->createPropFindJob(d->mUrl.url(), props); job->addMetaData(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true")); job->setProperty("davType", QStringLiteral("propFind")); job->setProperty("itemsMimeType", mimeType); connect(job, &KIO::DavJob::result, this, &DavItemsListJob::davJobFinished); } } } if (d->mSubJobCount == 0) { setError(ERR_ITEMLIST_NOMIMETYPE); setErrorTextFromDavError(); emitResult(); } } DavItem::List DavItemsListJob::items() const { return d->mItems; } DavItem::List DavItemsListJob::changedItems() const { return d->mChangedItems; } QStringList DavItemsListJob::deletedItems() const { return d->mDeletedItems; } void DavItemsListJob::davJobFinished(KJob *job) { KIO::DavJob *davJob = qobject_cast(job); const int responseCode = davJob->queryMetaData(QStringLiteral("responsecode")).isEmpty() ? 0 : davJob->queryMetaData(QStringLiteral("responsecode")).toInt(); // KIO::DavJob does not set error() even if the HTTP status code is a 4xx or a 5xx if (davJob->error() || (responseCode >= 400 && responseCode < 600)) { setLatestResponseCode(responseCode); setError(ERR_PROBLEM_WITH_REQUEST); setJobErrorText(davJob->errorText()); setJobError(davJob->error()); setErrorTextFromDavError(); } else { /* * Extract data from a document like the following: * * * * /caldav.php/test1.user/home/KOrganizer-166749289.780.ics * * * "b4bbea0278f4f63854c4167a7656024a" * * HTTP/1.1 200 OK * * * * /caldav.php/test1.user/home/KOrganizer-399416366.464.ics * * * "52eb129018398a7da4f435b2bc4c6cd5" * * HTTP/1.1 200 OK * * * */ const QString itemsMimeType = job->property("itemsMimeType").toString(); const QDomDocument document = davJob->response(); const QDomElement documentElement = document.documentElement(); QDomElement responseElement = Utils::firstChildElementNS(documentElement, QStringLiteral("DAV:"), QStringLiteral("response")); while (!responseElement.isNull()) { QDomElement propstatElement; // check for the valid propstat, without giving up on first error { const QDomNodeList propstats = responseElement.elementsByTagNameNS(QStringLiteral("DAV:"), QStringLiteral("propstat")); for (int i = 0; i < propstats.length(); ++i) { const QDomElement propstatCandidate = propstats.item(i).toElement(); const QDomElement statusElement = Utils::firstChildElementNS(propstatCandidate, QStringLiteral("DAV:"), QStringLiteral("status")); if (statusElement.text().contains(QStringLiteral("200"))) { propstatElement = propstatCandidate; } } } if (propstatElement.isNull()) { responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); continue; } const QDomElement propElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop")); // check whether it is a dav collection ... const QDomElement resourcetypeElement = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("resourcetype")); if (!responseElement.isNull()) { const QDomElement collectionElement = Utils::firstChildElementNS(resourcetypeElement, QStringLiteral("DAV:"), QStringLiteral("collection")); if (!collectionElement.isNull()) { responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); continue; } } // ... if not it is an item DavItem item; item.setContentType(itemsMimeType); // extract path const QDomElement hrefElement = Utils::firstChildElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("href")); const QString href = hrefElement.text(); - QUrl url = QUrl::fromUserInput(href); + QUrl url = davJob->url(); url.setUserInfo(QString()); if (href.startsWith(QLatin1Char('/'))) { - // href is a relative URL (i.e. missing scheme, hostname and so on) - // QUrl would treat this as a file URL be default, avoid that by setting - // empty scheme. - // Note: We don't want to expand this into a full URL (including hostname) - // as some CalDav implementations don't seem to handle that correctly. - url.setScheme(QString()); + // href is only a path, use request url to complete + url.setPath(href, QUrl::TolerantMode); + } else { + // href is a complete url + url = QUrl::fromUserInput(href); } QString itemUrl = url.toDisplayString(); if (d->mSeenUrls.contains(itemUrl)) { responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); continue; } d->mSeenUrls << itemUrl; auto _url = url; _url.setUserInfo(d->mUrl.url().userInfo()); item.setUrl(DavUrl(_url, d->mUrl.protocol())); // extract etag const QDomElement getetagElement = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("getetag")); item.setEtag(getetagElement.text()); d->mItems << item; if (d->mEtagCache->etagChanged(itemUrl, item.etag())) { d->mChangedItems << item; } responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); } } QSet removed = d->mEtagCache->urls().toSet(); removed.subtract(d->mSeenUrls); d->mDeletedItems = removed.toList(); if (--d->mSubJobCount == 0) { emitResult(); } } diff --git a/src/protocols/caldavprotocol.cpp b/src/protocols/caldavprotocol.cpp index 8414163..dd1d269 100644 --- a/src/protocols/caldavprotocol.cpp +++ b/src/protocols/caldavprotocol.cpp @@ -1,420 +1,421 @@ /* Copyright (c) 2009 Grégory Oestreicher This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "caldavprotocol.h" #include "common/utils.h" #include #include #include using namespace KDAV; class CaldavCollectionQueryBuilder : public XMLQueryBuilder { public: QDomDocument buildQuery() const override { QDomDocument document; QDomElement propfindElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("propfind")); document.appendChild(propfindElement); QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop")); propfindElement.appendChild(propElement); propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("displayname"))); propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("resourcetype"))); propElement.appendChild(document.createElementNS(QStringLiteral("http://apple.com/ns/ical/"), QStringLiteral("calendar-color"))); propElement.appendChild(document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("supported-calendar-component-set"))); propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("current-user-privilege-set"))); propElement.appendChild(document.createElementNS(QStringLiteral("http://calendarserver.org/ns/"), QStringLiteral("getctag"))); return document; } QString mimeType() const override { return QString(); } }; class CaldavListEventQueryBuilder : public XMLQueryBuilder { public: QDomDocument buildQuery() const override { QString startTime = parameter(QStringLiteral("start")).toString(); QString endTime = parameter(QStringLiteral("end")).toString(); QDomDocument document; QDomElement queryElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-query")); document.appendChild(queryElement); QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop")); queryElement.appendChild(propElement); QDomElement getetagElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("getetag")); propElement.appendChild(getetagElement); QDomElement getRTypeElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("resourcetype")); propElement.appendChild(getRTypeElement); QDomElement filterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("filter")); queryElement.appendChild(filterElement); QDomElement compfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter")); QDomAttr nameAttribute = document.createAttribute(QStringLiteral("name")); nameAttribute.setValue(QStringLiteral("VCALENDAR")); compfilterElement.setAttributeNode(nameAttribute); filterElement.appendChild(compfilterElement); QDomElement subcompfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter")); nameAttribute = document.createAttribute(QStringLiteral("name")); nameAttribute.setValue(QStringLiteral("VEVENT")); subcompfilterElement.setAttributeNode(nameAttribute); if (!startTime.isEmpty() || !endTime.isEmpty()) { QDomElement timeRangeElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("time-range")); if (!startTime.isEmpty()) { QDomAttr startAttribute = document.createAttribute(QStringLiteral("start")); startAttribute.setValue(startTime); timeRangeElement.setAttributeNode(startAttribute); } if (!endTime.isEmpty()) { QDomAttr endAttribute = document.createAttribute(QStringLiteral("end")); endAttribute.setValue(endTime); timeRangeElement.setAttributeNode(endAttribute); } subcompfilterElement.appendChild(timeRangeElement); } compfilterElement.appendChild(subcompfilterElement); return document; } QString mimeType() const override { return QStringLiteral("application/x-vnd.akonadi.calendar.event"); } }; class CaldavListTodoQueryBuilder : public XMLQueryBuilder { public: QDomDocument buildQuery() const override { QString startTime = parameter(QStringLiteral("start")).toString(); QString endTime = parameter(QStringLiteral("end")).toString(); QDomDocument document; QDomElement queryElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-query")); document.appendChild(queryElement); QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop")); queryElement.appendChild(propElement); QDomElement getetagElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("getetag")); propElement.appendChild(getetagElement); QDomElement getRTypeElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("resourcetype")); propElement.appendChild(getRTypeElement); QDomElement filterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("filter")); queryElement.appendChild(filterElement); QDomElement compfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter")); QDomAttr nameAttribute = document.createAttribute(QStringLiteral("name")); nameAttribute.setValue(QStringLiteral("VCALENDAR")); compfilterElement.setAttributeNode(nameAttribute); filterElement.appendChild(compfilterElement); QDomElement subcompfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter")); nameAttribute = document.createAttribute(QStringLiteral("name")); nameAttribute.setValue(QStringLiteral("VTODO")); subcompfilterElement.setAttributeNode(nameAttribute); if (!startTime.isEmpty() || !endTime.isEmpty()) { QDomElement timeRangeElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("time-range")); if (!startTime.isEmpty()) { QDomAttr startAttribute = document.createAttribute(QStringLiteral("start")); startAttribute.setValue(startTime); timeRangeElement.setAttributeNode(startAttribute); } if (!endTime.isEmpty()) { QDomAttr endAttribute = document.createAttribute(QStringLiteral("end")); endAttribute.setValue(endTime); timeRangeElement.setAttributeNode(endAttribute); } subcompfilterElement.appendChild(timeRangeElement); } compfilterElement.appendChild(subcompfilterElement); return document; } QString mimeType() const override { return QStringLiteral("application/x-vnd.akonadi.calendar.todo"); } }; class CaldavListJournalQueryBuilder : public XMLQueryBuilder { public: QDomDocument buildQuery() const override { QString startTime = parameter(QStringLiteral("start")).toString(); QString endTime = parameter(QStringLiteral("end")).toString(); QDomDocument document; QDomElement queryElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-query")); document.appendChild(queryElement); QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop")); queryElement.appendChild(propElement); QDomElement getetagElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("getetag")); propElement.appendChild(getetagElement); QDomElement getRTypeElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("resourcetype")); propElement.appendChild(getRTypeElement); QDomElement filterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("filter")); queryElement.appendChild(filterElement); QDomElement compfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter")); QDomAttr nameAttribute = document.createAttribute(QStringLiteral("name")); nameAttribute.setValue(QStringLiteral("VCALENDAR")); compfilterElement.setAttributeNode(nameAttribute); filterElement.appendChild(compfilterElement); QDomElement subcompfilterElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp-filter")); nameAttribute = document.createAttribute(QStringLiteral("name")); nameAttribute.setValue(QStringLiteral("VJOURNAL")); subcompfilterElement.setAttributeNode(nameAttribute); if (!startTime.isEmpty() || !endTime.isEmpty()) { QDomElement timeRangeElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("time-range")); if (!startTime.isEmpty()) { QDomAttr startAttribute = document.createAttribute(QStringLiteral("start")); startAttribute.setValue(startTime); timeRangeElement.setAttributeNode(startAttribute); } if (!endTime.isEmpty()) { QDomAttr endAttribute = document.createAttribute(QStringLiteral("end")); endAttribute.setValue(endTime); timeRangeElement.setAttributeNode(endAttribute); } subcompfilterElement.appendChild(timeRangeElement); } compfilterElement.appendChild(subcompfilterElement); return document; } QString mimeType() const override { return QStringLiteral("application/x-vnd.akonadi.calendar.journal"); } }; class CaldavMultigetQueryBuilder : public XMLQueryBuilder { public: QDomDocument buildQuery() const override { QDomDocument document; const QStringList urls = parameter(QStringLiteral("urls")).toStringList(); if (urls.isEmpty()) { return document; } QDomElement multigetElement = document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-multiget")); document.appendChild(multigetElement); QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop")); multigetElement.appendChild(propElement); propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("getetag"))); propElement.appendChild(document.createElementNS(QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("calendar-data"))); for (const QString &url : urls) { QDomElement hrefElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("href")); - const QDomText textNode = document.createTextNode(url); + const QUrl pathUrl = QUrl::fromUserInput(url); + const QDomText textNode = document.createTextNode(pathUrl.toString()); hrefElement.appendChild(textNode); multigetElement.appendChild(hrefElement); } return document; } QString mimeType() const override { return QString(); } }; CaldavProtocol::CaldavProtocol() { } bool CaldavProtocol::supportsPrincipals() const { return true; } bool CaldavProtocol::useReport() const { return true; } bool CaldavProtocol::useMultiget() const { return true; } QString CaldavProtocol::principalHomeSet() const { return QStringLiteral("calendar-home-set"); } QString CaldavProtocol::principalHomeSetNS() const { return QStringLiteral("urn:ietf:params:xml:ns:caldav"); } XMLQueryBuilder::Ptr CaldavProtocol::collectionsQuery() const { return XMLQueryBuilder::Ptr(new CaldavCollectionQueryBuilder()); } QString CaldavProtocol::collectionsXQuery() const { //const QString query( "//*[local-name()='calendar' and namespace-uri()='urn:ietf:params:xml:ns:caldav']/ancestor::*[local-name()='prop' and namespace-uri()='DAV:']/*[local-name()='supported-calendar-component-set' and namespace-uri()='urn:ietf:params:xml:ns:caldav']/*[local-name()='comp' and namespace-uri()='urn:ietf:params:xml:ns:caldav' and (@name='VTODO' or @name='VEVENT')]/ancestor::*[local-name()='response' and namespace-uri()='DAV:']" ); const QString query(QStringLiteral("//*[local-name()='calendar' and namespace-uri()='urn:ietf:params:xml:ns:caldav']/ancestor::*[local-name()='prop' and namespace-uri()='DAV:']/ancestor::*[local-name()='response' and namespace-uri()='DAV:']")); return query; } QVector CaldavProtocol::itemsQueries() const { QVector ret; ret << XMLQueryBuilder::Ptr(new CaldavListEventQueryBuilder()); ret << XMLQueryBuilder::Ptr(new CaldavListTodoQueryBuilder()); ret << XMLQueryBuilder::Ptr(new CaldavListJournalQueryBuilder()); return ret; } XMLQueryBuilder::Ptr CaldavProtocol::itemsReportQuery(const QStringList &urls) const { XMLQueryBuilder::Ptr ret(new CaldavMultigetQueryBuilder()); ret->setParameter(QStringLiteral("urls"), urls); return ret; } QString CaldavProtocol::responseNamespace() const { return QStringLiteral("urn:ietf:params:xml:ns:caldav"); } QString CaldavProtocol::dataTagName() const { return QStringLiteral("calendar-data"); } DavCollection::ContentTypes CaldavProtocol::collectionContentTypes(const QDomElement &propstatElement) const { /* * Extract the content type information from a propstat like the following * * * * * * * * * * * * * * * Test1 User * * HTTP/1.1 200 OK * */ const QDomElement propElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop")); const QDomElement supportedcomponentElement = Utils::firstChildElementNS(propElement, QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("supported-calendar-component-set")); DavCollection::ContentTypes contentTypes; QDomElement compElement = Utils::firstChildElementNS(supportedcomponentElement, QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp")); /* * Assign the content-type if the server didn't return anything. * According to RFC4791, §5.2.3: * In the absence of this property, the server MUST accept all * component types, and the client can assume that all component * types are accepted. */ if (compElement.isNull()) { contentTypes |= DavCollection::Calendar; contentTypes |= DavCollection::Events; contentTypes |= DavCollection::Todos; contentTypes |= DavCollection::FreeBusy; contentTypes |= DavCollection::Journal; } while (!compElement.isNull()) { const QString type = compElement.attribute(QStringLiteral("name")).toLower(); if (type == QLatin1String("vcalendar")) { contentTypes |= DavCollection::Calendar; } else if (type == QLatin1String("vevent")) { contentTypes |= DavCollection::Events; } else if (type == QLatin1String("vtodo")) { contentTypes |= DavCollection::Todos; } else if (type == QLatin1String("vfreebusy")) { contentTypes |= DavCollection::FreeBusy; } else if (type == QLatin1String("vjournal")) { contentTypes |= DavCollection::Journal; } compElement = Utils::nextSiblingElementNS(compElement, QStringLiteral("urn:ietf:params:xml:ns:caldav"), QStringLiteral("comp")); } return contentTypes; }