File indexing completed on 2025-02-16 04:49:13

0001 /****************************************************************************
0002 ** QWebDAV Library (qwebdavlib) - LGPL v2.1
0003 **
0004 ** HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)
0005 ** from June 2007
0006 **      http://tools.ietf.org/html/rfc4918
0007 **
0008 ** Web Distributed Authoring and Versioning (WebDAV) SEARCH
0009 ** from November 2008
0010 **      http://tools.ietf.org/html/rfc5323
0011 **
0012 ** Missing:
0013 **      - LOCK support
0014 **      - process WebDAV SEARCH responses
0015 **
0016 ** Copyright (C) 2012 Martin Haller <martin.haller@rebnil.com>
0017 ** for QWebDAV library (qwebdavlib) version 1.0
0018 **      https://github.com/mhaller/qwebdavlib
0019 **
0020 ** Copyright (C) 2012 Timo Zimmermann <meedav@timozimmermann.de>
0021 ** for portions from QWebdav plugin for MeeDav (LGPL v2.1)
0022 **      http://projects.developer.nokia.com/meedav/
0023 **
0024 ** Copyright (C) 2009-2010 Corentin Chary <corentin.chary@gmail.com>
0025 ** for portions from QWebdav - WebDAV lib for Qt4 (LGPL v2.1)
0026 **      http://xf.iksaif.net/dev/qwebdav.html
0027 **
0028 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
0029 ** for naturalCompare() (LGPL v2.1)
0030 **      http://qt.gitorious.org/qt/qt/blobs/4.7/src/gui/dialogs/qfilesystemmodel.cpp
0031 **
0032 ** This library is free software; you can redistribute it and/or
0033 ** modify it under the terms of the GNU Library General Public
0034 ** License as published by the Free Software Foundation; either
0035 ** version 2 of the License, or (at your option) any later version.
0036 **
0037 ** This library is distributed in the hope that it will be useful,
0038 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
0039 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0040 ** Library General Public License for more details.
0041 **
0042 ** You should have received a copy of the GNU Library General Public License
0043 ** along with this library; see the file COPYING.LIB.  If not, write to
0044 ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0045 ** Boston, MA 02110-1301, USA.
0046 **
0047 ** http://www.gnu.org/licenses/lgpl-2.1-standalone.html
0048 **
0049 ****************************************************************************/
0050 
0051 #include "qwebdav.h"
0052 
0053 #include <QDebug>
0054 #include <QTextStream>
0055 
0056 Q_LOGGING_CATEGORY(KDAV2_LOG, "org.kde.pim.kdav2.webdav")
0057 
0058 QWebdav::QWebdav (QObject *parent) : QNetworkAccessManager(parent)
0059   ,m_rootPath()
0060   ,m_username()
0061   ,m_password()
0062   ,m_baseUrl()
0063   ,m_currentConnectionType(QWebdav::HTTP)
0064   ,m_authenticator_lastReply(nullptr)
0065 
0066 {
0067     qRegisterMetaType<QNetworkReply*>("QNetworkReply*");
0068 
0069     connect(this, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), this, SLOT(provideAuthenication(QNetworkReply*,QAuthenticator*)));
0070     connect(this, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), this, SLOT(sslErrors(QNetworkReply*,QList<QSslError>)));
0071 }
0072 
0073 QWebdav::~QWebdav()
0074 {
0075 }
0076 
0077 QString QWebdav::hostname() const
0078 {
0079     return m_baseUrl.host();
0080 }
0081 
0082 int QWebdav::port() const
0083 {
0084     return m_baseUrl.port();
0085 }
0086 
0087 QString QWebdav::rootPath() const
0088 {
0089     return m_rootPath;
0090 }
0091 
0092 QString QWebdav::username() const
0093 {
0094     return m_username;
0095 }
0096 
0097 QString QWebdav::password() const
0098 {
0099     return m_password;
0100 }
0101 
0102 QWebdav::QWebdavConnectionType QWebdav::connectionType() const
0103 {
0104     return m_currentConnectionType;
0105 }
0106 
0107 bool QWebdav::isSSL() const
0108 {
0109     return (m_currentConnectionType==QWebdav::HTTPS);
0110 }
0111 
0112 void QWebdav::setConnectionSettings(const QWebdavConnectionType connectionType,
0113                                     const QString& hostname,
0114                                     const QString& rootPath,
0115                                     const QString& username,
0116                                     const QString& password,
0117                                     int port,
0118                                     bool ignoreSslErrors)
0119 {
0120     m_rootPath = rootPath;
0121 
0122     if ((m_rootPath.size()>0) && (m_rootPath.endsWith("/")))
0123         m_rootPath.chop(1);
0124 
0125     QString uriScheme;
0126     switch (connectionType)
0127     {
0128     case QWebdav::HTTP:
0129         uriScheme = "http";
0130         break;
0131     case QWebdav::HTTPS:
0132         uriScheme = "https";
0133         break;
0134     }
0135 
0136     m_currentConnectionType = connectionType;
0137 
0138     m_baseUrl.setScheme(uriScheme);
0139     m_baseUrl.setHost(hostname);
0140     m_baseUrl.setPath(rootPath);
0141 
0142     if (port != 0) {
0143 
0144         // use user-defined port number
0145         if ( ! ( ( (port == 80) && (m_currentConnectionType==QWebdav::HTTP) ) ||
0146                ( (port == 443) && (m_currentConnectionType==QWebdav::HTTPS) ) ) )
0147             m_baseUrl.setPort(port);
0148     }
0149 
0150     m_ignoreSslErrors = ignoreSslErrors;
0151 
0152     m_username = username;
0153     m_password = password;
0154 }
0155 
0156 void QWebdav::provideAuthenication(QNetworkReply *reply, QAuthenticator *authenticator)
0157 {
0158     qCDebug(KDAV2_LOG) << "QWebdav::authenticationRequired()  option == " << authenticator->options();
0159 
0160     if (reply == m_authenticator_lastReply) {
0161         //Avoid endless retries. This will fail with AuthenticationRequiredError
0162         return;
0163     }
0164     m_authenticator_lastReply = reply;
0165 
0166     authenticator->setUser(m_username);
0167     authenticator->setPassword(m_password);
0168 }
0169 
0170 void QWebdav::sslErrors(QNetworkReply *reply, const QList<QSslError> &)
0171 {
0172     qCDebug(KDAV2_LOG) << "QWebdav::sslErrors()   reply->url == " << reply->url().toString(QUrl::RemoveUserInfo);
0173 
0174     if (m_ignoreSslErrors) {
0175         // user accepted this SSL certifcate already ==> ignore SSL errors
0176         reply->ignoreSslErrors();
0177     }
0178 }
0179 
0180 QString QWebdav::absolutePath(const QString &relPath)
0181 {
0182     return QString(m_rootPath + relPath);
0183 
0184 }
0185 
0186 QNetworkReply* QWebdav::createDAVRequest(const QString& method, QNetworkRequest& req, const QByteArray& outgoingData)
0187 {
0188     if(!outgoingData.isEmpty()) {
0189         req.setHeader(QNetworkRequest::ContentLengthHeader, outgoingData.size());
0190         req.setHeader(QNetworkRequest::ContentTypeHeader, "text/xml; charset=utf-8");
0191     }
0192 
0193     qCDebug(KDAV2_LOG) << " QWebdav::createDAVRequest\n"
0194         << "   " << method << " " << req.url().toString();
0195     for (const auto &rawHeaderItem : req.rawHeaderList()) {
0196         qCDebug(KDAV2_LOG) << "   " << rawHeaderItem << ": " << req.rawHeader(rawHeaderItem);
0197     }
0198 
0199     if (KDAV2_LOG().isDebugEnabled()) {
0200         QTextStream stream(stdout, QIODevice::WriteOnly);
0201         stream << outgoingData;
0202     }
0203 
0204     auto reply = sendCustomRequest(req, method.toLatin1(), outgoingData);
0205     //For redirects
0206     reply->setProperty("requestData", outgoingData);
0207     return reply;
0208 }
0209 
0210 QNetworkReply* QWebdav::list(const QString& path, int depth)
0211 {
0212     QWebdav::PropNames query;
0213     QStringList props;
0214 
0215     // Small set of properties
0216     // href in response contains also the name
0217     // e.g. /container/front.html
0218     props << "getlastmodified";         // http://www.webdav.org/specs/rfc4918.html#PROPERTY_getlastmodified
0219     // e.g. Mon, 12 Jan 1998 09:25:56 GMT
0220     props << "getcontentlength";        // http://www.webdav.org/specs/rfc4918.html#PROPERTY_getcontentlength
0221     // e.g. "4525"
0222     props << "resourcetype";            // http://www.webdav.org/specs/rfc4918.html#PROPERTY_resourcetype
0223     // e.g. "collection" for a directory
0224 
0225     // Following properties are available as well.
0226     //props << "creationdate";          // http://www.webdav.org/specs/rfc4918.html#PROPERTY_creationdate
0227     // e.g. "1997-12-01T18:27:21-08:00"
0228     //props << "displayname";           // http://www.webdav.org/specs/rfc4918.html#PROPERTY_displayname
0229     // e.g. "Example HTML resource"
0230     //props << "getcontentlanguage";    // http://www.webdav.org/specs/rfc4918.html#PROPERTY_getcontentlanguage
0231     // e.g. "en-US"
0232     //props << "getcontenttype";        // http://www.webdav.org/specs/rfc4918.html#PROPERTY_getcontenttype
0233     // e.g "text/html"
0234     //props << "getetag";               // http://www.webdav.org/specs/rfc4918.html#PROPERTY_getetag
0235     // e.g. "zzyzx"
0236 
0237     // Additionally, there are also properties for locking
0238 
0239     query["DAV:"] = props;
0240 
0241     return propfind(path, query, depth);
0242 }
0243 
0244 QNetworkReply* QWebdav::search(const QString& path, const QString& q )
0245 {
0246     QByteArray query = "<?xml version=\"1.0\"?>\r\n";
0247     query.append( "<D:searchrequest xmlns:D=\"DAV:\">\r\n" );
0248     query.append( q.toUtf8() );
0249     query.append( "</D:searchrequest>\r\n" );
0250 
0251     QNetworkRequest req;
0252 
0253     QUrl reqUrl(m_baseUrl);
0254     reqUrl.setPath(absolutePath(path));
0255 
0256     req.setUrl(reqUrl);
0257 
0258     return this->createDAVRequest("SEARCH", req, query);
0259 }
0260 
0261 QNetworkReply* QWebdav::get(const QString& path, const QMap<QByteArray, QByteArray> &headers)
0262 {
0263     QNetworkRequest req;
0264 
0265     QUrl reqUrl(m_baseUrl);
0266     reqUrl.setPath(absolutePath(path));
0267 
0268     for (auto it = headers.constBegin(); it != headers.constEnd(); it++) {
0269         req.setRawHeader(it.key(), it.value());
0270     }
0271 
0272     qCDebug(KDAV2_LOG) << "QWebdav::get() url = " << req.url().toString(QUrl::RemoveUserInfo);
0273 
0274     req.setUrl(reqUrl);
0275 
0276     return QNetworkAccessManager::get(req);
0277 }
0278 
0279 QNetworkReply* QWebdav::put(const QString& path, const QByteArray& data, const QMap<QByteArray, QByteArray> &headers)
0280 {
0281     QNetworkRequest req;
0282 
0283     QUrl reqUrl(m_baseUrl);
0284     reqUrl.setPath(absolutePath(path));
0285 
0286     req.setUrl(reqUrl);
0287     for (auto it = headers.constBegin(); it != headers.constEnd(); it++) {
0288         req.setRawHeader(it.key(), it.value());
0289     }
0290 
0291     qCDebug(KDAV2_LOG) << "QWebdav::put() url = " << req.url().toString(QUrl::RemoveUserInfo);
0292 
0293     auto reply =  QNetworkAccessManager::put(req, data);
0294     reply->setProperty("requestData", data);
0295     reply->setProperty("isPut", true);
0296     return reply;
0297 }
0298 
0299 
0300 QNetworkReply* QWebdav::propfind(const QString& path, const QWebdav::PropNames& props, int depth)
0301 {
0302     QByteArray query;
0303 
0304     query = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>";
0305     query += "<D:propfind xmlns:D=\"DAV:\" >";
0306     query += "<D:prop>";
0307     foreach (QString ns, props.keys())
0308     {
0309         foreach (const QString key, props[ns])
0310             if (ns == "DAV:")
0311                 query += "<D:" + key + "/>";
0312             else
0313                 query += "<" + key + " xmlns=\"" + ns + "\"/>";
0314     }
0315     query += "</D:prop>";
0316     query += "</D:propfind>";
0317     return propfind(path, query, depth);
0318 }
0319 
0320 
0321 QNetworkReply* QWebdav::propfind(const QString& path, const QByteArray& query, int depth)
0322 {
0323     QNetworkRequest req;
0324 
0325     QUrl reqUrl(m_baseUrl);
0326     reqUrl.setPath(absolutePath(path));
0327 
0328     req.setUrl(reqUrl);
0329     req.setRawHeader("Depth", depth == 2 ? QString("infinity").toUtf8() : QString::number(depth).toUtf8());
0330 
0331     return createDAVRequest("PROPFIND", req, query);
0332 }
0333 
0334 QNetworkReply* QWebdav::report(const QString& path, const QByteArray& query, int depth)
0335 {
0336     QNetworkRequest req;
0337 
0338     QUrl reqUrl(m_baseUrl);
0339     reqUrl.setPath(absolutePath(path));
0340 
0341     req.setUrl(reqUrl);
0342     req.setRawHeader("Depth", depth == 2 ? QString("infinity").toUtf8() : QString::number(depth).toUtf8());
0343 
0344     return createDAVRequest("REPORT", req, query);
0345 }
0346 
0347 QNetworkReply* QWebdav::proppatch(const QString& path, const QWebdav::PropValues& props)
0348 {
0349     QByteArray query;
0350 
0351     query = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>";
0352     query += "<D:proppatch xmlns:D=\"DAV:\" >";
0353     query += "<D:prop>";
0354     foreach (QString ns, props.keys())
0355     {
0356         QMap < QString , QVariant >::const_iterator i;
0357 
0358         for (i = props[ns].constBegin(); i != props[ns].constEnd(); ++i) {
0359             if (ns == "DAV:") {
0360                 query += "<D:" + i.key() + ">";
0361                 query += i.value().toString();
0362                 query += "</D:" + i.key() + ">" ;
0363             } else {
0364                 query += "<" + i.key() + " xmlns=\"" + ns + "\">";
0365                 query += i.value().toString();
0366                 query += "</" + i.key() + " xmlns=\"" + ns + "\"/>";
0367             }
0368         }
0369     }
0370     query += "</D:prop>";
0371     query += "</D:propfind>";
0372 
0373     return proppatch(path, query);
0374 }
0375 
0376 QNetworkReply* QWebdav::proppatch(const QString& path, const QByteArray& query)
0377 {
0378     QNetworkRequest req;
0379 
0380     QUrl reqUrl(m_baseUrl);
0381     reqUrl.setPath(absolutePath(path));
0382 
0383     req.setUrl(reqUrl);
0384 
0385     return createDAVRequest("PROPPATCH", req, query);
0386 }
0387 
0388 QNetworkReply* QWebdav::mkdir (const QString& path)
0389 {
0390     QNetworkRequest req;
0391 
0392     QUrl reqUrl(m_baseUrl);
0393     reqUrl.setPath(absolutePath(path));
0394 
0395     req.setUrl(reqUrl);
0396 
0397     return createDAVRequest("MKCOL", req);
0398 }
0399 
0400 QNetworkReply* QWebdav::mkdir (const QString& path, const QByteArray& query)
0401 {
0402     QNetworkRequest req;
0403 
0404     QUrl reqUrl(m_baseUrl);
0405     reqUrl.setPath(absolutePath(path));
0406 
0407     req.setUrl(reqUrl);
0408 
0409     return createDAVRequest("MKCOL", req, query);
0410 }
0411 
0412 QNetworkReply* QWebdav::mkcalendar (const QString& path, const QByteArray& query)
0413 {
0414     QNetworkRequest req;
0415 
0416     QUrl reqUrl(m_baseUrl);
0417     reqUrl.setPath(absolutePath(path));
0418 
0419     req.setUrl(reqUrl);
0420 
0421     return createDAVRequest("MKCALENDAR", req, query);
0422 }
0423 
0424 QNetworkReply* QWebdav::copy(const QString& pathFrom, const QString& pathTo, bool overwrite)
0425 {
0426     QNetworkRequest req;
0427 
0428     QUrl reqUrl(m_baseUrl);
0429     reqUrl.setPath(absolutePath(pathFrom));
0430 
0431     req.setUrl(reqUrl);
0432 
0433     // RFC4918 Section 10.3 requires an absolute URI for destination raw header
0434     //  http://tools.ietf.org/html/rfc4918#section-10.3
0435     // RFC3986 Section 4.3 specifies the term absolute URI
0436     //  http://tools.ietf.org/html/rfc3986#section-4.3
0437     QUrl dstUrl(m_baseUrl);
0438     //dstUrl.setUserInfo("");
0439     dstUrl.setPath(absolutePath(pathTo));
0440     req.setRawHeader("Destination", dstUrl.toString().toUtf8());
0441 
0442     req.setRawHeader("Depth", "infinity");
0443     req.setRawHeader("Overwrite", overwrite ? "T" : "F");
0444 
0445     return createDAVRequest("COPY", req);
0446 }
0447 
0448 QNetworkReply* QWebdav::move(const QString& pathFrom, const QString& pathTo, bool overwrite)
0449 {
0450     QNetworkRequest req;
0451 
0452     QUrl reqUrl(m_baseUrl);
0453     reqUrl.setPath(absolutePath(pathFrom));
0454 
0455     req.setUrl(reqUrl);
0456 
0457     // RFC4918 Section 10.3 requires an absolute URI for destination raw header
0458     //  http://tools.ietf.org/html/rfc4918#section-10.3
0459     // RFC3986 Section 4.3 specifies the term absolute URI
0460     //  http://tools.ietf.org/html/rfc3986#section-4.3
0461     QUrl dstUrl(m_baseUrl);
0462     //dstUrl.setUserInfo("");
0463     dstUrl.setPath(absolutePath(pathTo));
0464     req.setRawHeader("Destination", dstUrl.toString().toUtf8());
0465 
0466     req.setRawHeader("Depth", "infinity");
0467     req.setRawHeader("Overwrite", overwrite ? "T" : "F");
0468 
0469     return createDAVRequest("MOVE", req);
0470 }
0471 
0472 QNetworkReply* QWebdav::remove(const QString& path)
0473 {
0474     QNetworkRequest req;
0475 
0476     QUrl reqUrl(m_baseUrl);
0477     reqUrl.setPath(absolutePath(path));
0478 
0479     req.setUrl(reqUrl);
0480 
0481     return createDAVRequest("DELETE", req);
0482 }