1 /*
2     SPDX-FileCopyrightText: 2010 Grégory Oestreicher <greg@kamago.net>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "davcollectionmodifyjob.h"
8 #include "davjobbase_p.h"
9 #include "davmanager_p.h"
10 
11 #include "daverror.h"
12 #include "utils_p.h"
13 
14 #include <KIO/DavJob>
15 #include <KIO/Job>
16 
17 using namespace KDAV;
18 
19 namespace KDAV
20 {
21 class DavCollectionModifyJobPrivate : public DavJobBasePrivate
22 {
23 public:
24     void davJobFinished(KJob *job);
25 
26     DavUrl mUrl;
27     QDomDocument mQuery;
28 
29     QVector<QDomElement> mSetProperties;
30     QVector<QDomElement> mRemoveProperties;
31 };
32 }
33 
DavCollectionModifyJob(const DavUrl & url,QObject * parent)34 DavCollectionModifyJob::DavCollectionModifyJob(const DavUrl &url, QObject *parent)
35     : DavJobBase(new DavCollectionModifyJobPrivate, parent)
36 {
37     Q_D(DavCollectionModifyJob);
38     d->mUrl = url;
39 }
40 
setProperty(const QString & prop,const QString & value,const QString & ns)41 void DavCollectionModifyJob::setProperty(const QString &prop, const QString &value, const QString &ns)
42 {
43     Q_D(DavCollectionModifyJob);
44     QDomElement propElement;
45 
46     if (ns.isEmpty()) {
47         propElement = d->mQuery.createElement(prop);
48     } else {
49         propElement = d->mQuery.createElementNS(ns, prop);
50     }
51 
52     const QDomText textElement = d->mQuery.createTextNode(value);
53     propElement.appendChild(textElement);
54 
55     d->mSetProperties << propElement;
56 }
57 
removeProperty(const QString & prop,const QString & ns)58 void DavCollectionModifyJob::removeProperty(const QString &prop, const QString &ns)
59 {
60     Q_D(DavCollectionModifyJob);
61     QDomElement propElement;
62 
63     if (ns.isEmpty()) {
64         propElement = d->mQuery.createElement(prop);
65     } else {
66         propElement = d->mQuery.createElementNS(ns, prop);
67     }
68 
69     d->mRemoveProperties << propElement;
70 }
71 
start()72 void DavCollectionModifyJob::start()
73 {
74     Q_D(DavCollectionModifyJob);
75     if (d->mSetProperties.isEmpty() && d->mRemoveProperties.isEmpty()) {
76         setError(ERR_COLLECTIONMODIFY_NO_PROPERITES);
77         d->setErrorTextFromDavError();
78         emitResult();
79         return;
80     }
81 
82     QDomDocument mQuery;
83     QDomElement propertyUpdateElement = mQuery.createElementNS(QStringLiteral("DAV:"), QStringLiteral("propertyupdate"));
84     mQuery.appendChild(propertyUpdateElement);
85 
86     if (!d->mSetProperties.isEmpty()) {
87         QDomElement setElement = mQuery.createElementNS(QStringLiteral("DAV:"), QStringLiteral("set"));
88         propertyUpdateElement.appendChild(setElement);
89 
90         QDomElement propElement = mQuery.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
91         setElement.appendChild(propElement);
92 
93         for (const QDomElement &element : std::as_const(d->mSetProperties)) {
94             propElement.appendChild(element);
95         }
96     }
97 
98     if (!d->mRemoveProperties.isEmpty()) {
99         QDomElement removeElement = mQuery.createElementNS(QStringLiteral("DAV:"), QStringLiteral("remove"));
100         propertyUpdateElement.appendChild(removeElement);
101 
102         QDomElement propElement = mQuery.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
103         removeElement.appendChild(propElement);
104 
105         for (const QDomElement &element : std::as_const(d->mSetProperties)) {
106             propElement.appendChild(element);
107         }
108     }
109 
110     KIO::DavJob *job = DavManager::self()->createPropPatchJob(d->mUrl.url(), mQuery.toString());
111     job->addMetaData(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true"));
112     connect(job, &KIO::DavJob::result, this, [d](KJob *job) {
113         d->davJobFinished(job);
114     });
115 }
116 
davJobFinished(KJob * job)117 void DavCollectionModifyJobPrivate::davJobFinished(KJob *job)
118 {
119     KIO::DavJob *davJob = qobject_cast<KIO::DavJob *>(job);
120     const QString responseCodeStr = davJob->queryMetaData(QStringLiteral("responsecode"));
121     const int responseCode = responseCodeStr.isEmpty() ? 0 : responseCodeStr.toInt();
122 
123     // KIO::DavJob does not set error() even if the HTTP status code is a 4xx or a 5xx
124     if (davJob->error() || (responseCode >= 400 && responseCode < 600)) {
125         setLatestResponseCode(responseCode);
126         setError(ERR_COLLECTIONMODIFY);
127         setJobErrorText(davJob->errorText());
128         setJobError(davJob->error());
129         setErrorTextFromDavError();
130         emitResult();
131         return;
132     }
133 
134     QDomDocument response;
135     response.setContent(davJob->responseData(), true);
136     QDomElement responseElement = Utils::firstChildElementNS(response.documentElement(), QStringLiteral("DAV:"), QStringLiteral("response"));
137 
138     bool hasError = false;
139 
140     // parse all propstats answers to get the eventual errors
141     const QDomNodeList propstats = responseElement.elementsByTagNameNS(QStringLiteral("DAV:"), QStringLiteral("propstat"));
142     for (int i = 0; i < propstats.length(); ++i) {
143         const QDomElement propstatElement = propstats.item(i).toElement();
144         const QDomElement statusElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("status"));
145 
146         const QString statusText = statusElement.text();
147         if (statusText.contains(QLatin1String("200"))) {
148             continue;
149         } else {
150             // Generic error
151             hasError = true;
152             break;
153         }
154     }
155 
156     if (hasError) {
157         setError(ERR_COLLECTIONMODIFY_RESPONSE);
158 
159         // Trying to get more information about the error
160         const QDomElement responseDescriptionElement =
161             Utils::firstChildElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("responsedescription"));
162         if (!responseDescriptionElement.isNull()) {
163             setJobErrorText(responseDescriptionElement.text());
164         }
165         setErrorTextFromDavError();
166     }
167 
168     emitResult();
169 }
170