1 /*
2  * Copyright (C) by Roeland Jago Douma <roeland@famdouma.nl>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12  * for more details.
13  */
14 
15 #include "ocsjob.h"
16 #include "networkjobs.h"
17 #include "account.h"
18 
19 #include <QBuffer>
20 #include <QJsonDocument>
21 #include <QJsonObject>
22 
23 namespace OCC {
24 
25 Q_LOGGING_CATEGORY(lcOcs, "nextcloud.gui.sharing.ocs", QtInfoMsg)
26 
OcsJob(AccountPtr account)27 OcsJob::OcsJob(AccountPtr account)
28     : AbstractNetworkJob(account, "")
29 {
30     _passStatusCodes.append(OCS_SUCCESS_STATUS_CODE);
31     _passStatusCodes.append(OCS_SUCCESS_STATUS_CODE_V2);
32     _passStatusCodes.append(OCS_NOT_MODIFIED_STATUS_CODE_V2);
33     setIgnoreCredentialFailure(true);
34 }
35 
setVerb(const QByteArray & verb)36 void OcsJob::setVerb(const QByteArray &verb)
37 {
38     _verb = verb;
39 }
40 
addParam(const QString & name,const QString & value)41 void OcsJob::addParam(const QString &name, const QString &value)
42 {
43     _params.append(qMakePair(name, value));
44 }
45 
addPassStatusCode(int code)46 void OcsJob::addPassStatusCode(int code)
47 {
48     _passStatusCodes.append(code);
49 }
50 
appendPath(const QString & id)51 void OcsJob::appendPath(const QString &id)
52 {
53     setPath(path() + QLatin1Char('/') + id);
54 }
55 
addRawHeader(const QByteArray & headerName,const QByteArray & value)56 void OcsJob::addRawHeader(const QByteArray &headerName, const QByteArray &value)
57 {
58     _request.setRawHeader(headerName, value);
59 }
60 
percentEncodeQueryItems(const QList<QPair<QString,QString>> & items)61 static QUrlQuery percentEncodeQueryItems(
62     const QList<QPair<QString, QString>> &items)
63 {
64     QUrlQuery result;
65     // Note: QUrlQuery::setQueryItems() does not fully percent encode
66     // the query items, see #5042
67     foreach (const auto &item, items) {
68         result.addQueryItem(
69             QUrl::toPercentEncoding(item.first),
70             QUrl::toPercentEncoding(item.second));
71     }
72     return result;
73 }
74 
start()75 void OcsJob::start()
76 {
77     addRawHeader("Ocs-APIREQUEST", "true");
78     addRawHeader("Content-Type", "application/x-www-form-urlencoded");
79 
80     auto *buffer = new QBuffer;
81 
82     QUrlQuery queryItems;
83     if (_verb == "GET") {
84         queryItems = percentEncodeQueryItems(_params);
85     } else if (_verb == "POST" || _verb == "PUT") {
86         // Url encode the _postParams and put them in a buffer.
87         QByteArray postData;
88         Q_FOREACH (auto tmp, _params) {
89             if (!postData.isEmpty()) {
90                 postData.append("&");
91             }
92             postData.append(QUrl::toPercentEncoding(tmp.first));
93             postData.append("=");
94             postData.append(QUrl::toPercentEncoding(tmp.second));
95         }
96         buffer->setData(postData);
97     }
98     queryItems.addQueryItem(QLatin1String("format"), QLatin1String("json"));
99     QUrl url = Utility::concatUrlPath(account()->url(), path(), queryItems);
100     sendRequest(_verb, url, _request, buffer);
101     AbstractNetworkJob::start();
102 }
103 
finished()104 bool OcsJob::finished()
105 {
106     const QByteArray replyData = reply()->readAll();
107 
108     QJsonParseError error;
109     QString message;
110     int statusCode = 0;
111     auto json = QJsonDocument::fromJson(replyData, &error);
112 
113     // when it is null we might have a 304 so get status code from reply() and gives a warning...
114     if (error.error != QJsonParseError::NoError) {
115         statusCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
116         qCWarning(lcOcs) << "Could not parse reply to"
117                          << _verb
118                          << Utility::concatUrlPath(account()->url(), path())
119                          << _params
120                          << error.errorString()
121                          << ":" << replyData;
122     } else {
123         statusCode  = getJsonReturnCode(json, message);
124     }
125 
126     //... then it checks for the statusCode
127     if (!_passStatusCodes.contains(statusCode)) {
128         qCWarning(lcOcs) << "Reply to"
129                          << _verb
130                          << Utility::concatUrlPath(account()->url(), path())
131                          << _params
132                          << "has unexpected status code:" << statusCode << replyData;
133         emit ocsError(statusCode, message);
134 
135     } else {
136         // save new ETag value
137         if(reply()->rawHeaderList().contains("ETag"))
138             emit etagResponseHeaderReceived(reply()->rawHeader("ETag"), statusCode);
139 
140         emit jobFinished(json, statusCode);
141     }
142     return true;
143 }
144 
getJsonReturnCode(const QJsonDocument & json,QString & message)145 int OcsJob::getJsonReturnCode(const QJsonDocument &json, QString &message)
146 {
147     //TODO proper checking
148     auto meta = json.object().value("ocs").toObject().value("meta").toObject();
149     int code = meta.value("statuscode").toInt();
150     message = meta.value("message").toString();
151 
152     return code;
153 }
154 }
155