aboutsummaryrefslogtreecommitdiff
path: root/jobs/basejob.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'jobs/basejob.cpp')
-rw-r--r--jobs/basejob.cpp116
1 files changed, 94 insertions, 22 deletions
diff --git a/jobs/basejob.cpp b/jobs/basejob.cpp
index 9df3e430..1f079966 100644
--- a/jobs/basejob.cpp
+++ b/jobs/basejob.cpp
@@ -25,7 +25,6 @@
#include <QtNetwork/QNetworkReply>
#include <QtCore/QTimer>
#include <QtCore/QRegularExpression>
-//#include <QtCore/QStringBuilder>
#include <array>
@@ -60,10 +59,16 @@ class BaseJob::Private
// Contents for the network request
HttpVerb verb;
QString apiEndpoint;
+ QHash<QByteArray, QByteArray> requestHeaders;
QUrlQuery requestQuery;
Data requestData;
bool needsToken;
+ // There's no use of QMimeType here because we don't want to match
+ // content types against the known MIME type hierarchy; and at the same
+ // type QMimeType is of little help with MIME type globs (`text/*` etc.)
+ QByteArrayList expectedContentTypes;
+
QScopedPointer<QNetworkReply, NetworkReplyDeleter> reply;
Status status = Pending;
@@ -90,11 +95,16 @@ QDebug QMatrixClient::operator<<(QDebug dbg, const BaseJob::Status& s)
<< QString(s.message).replace(filter, "\\1 HIDDEN");
}
+BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, bool needsToken)
+ : BaseJob(verb, name, endpoint, Query { }, Data { }, needsToken)
+{ }
+
BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint,
const Query& query, const Data& data, bool needsToken)
: d(new Private(verb, endpoint, query, data, needsToken))
{
setObjectName(name);
+ setExpectedContentTypes({ "application/json" });
d->timer.setSingleShot(true);
connect (&d->timer, &QTimer::timeout, this, &BaseJob::timeout);
d->retryTimer.setSingleShot(true);
@@ -117,6 +127,22 @@ void BaseJob::setApiEndpoint(const QString& apiEndpoint)
d->apiEndpoint = apiEndpoint;
}
+const BaseJob::headers_t&BaseJob::requestHeaders() const
+{
+ return d->requestHeaders;
+}
+
+void BaseJob::setRequestHeader(const headers_t::key_type& headerName,
+ const headers_t::mapped_type& headerValue)
+{
+ d->requestHeaders[headerName] = headerValue;
+}
+
+void BaseJob::setRequestHeaders(const BaseJob::headers_t& headers)
+{
+ d->requestHeaders = headers;
+}
+
const QUrlQuery& BaseJob::query() const
{
return d->requestQuery;
@@ -137,6 +163,21 @@ void BaseJob::setRequestData(const BaseJob::Data& data)
d->requestData = data;
}
+const QByteArrayList& BaseJob::expectedContentTypes() const
+{
+ return d->expectedContentTypes;
+}
+
+void BaseJob::addExpectedContentType(const QByteArray& contentType)
+{
+ d->expectedContentTypes << contentType;
+}
+
+void BaseJob::setExpectedContentTypes(const QByteArrayList& contentTypes)
+{
+ d->expectedContentTypes = contentTypes;
+}
+
void BaseJob::Private::sendRequest()
{
QUrl url = connection->baseUrl();
@@ -148,13 +189,16 @@ void BaseJob::Private::sendRequest()
url.setQuery(requestQuery);
QNetworkRequest req {url};
- req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+ if (!requestHeaders.contains("Content-Type"))
+ req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
req.setRawHeader(QByteArray("Authorization"),
QByteArray("Bearer ") + connection->accessToken());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
req.setMaximumRedirectsAllowed(10);
#endif
+ for (auto it = requestHeaders.cbegin(); it != requestHeaders.cend(); ++it)
+ req.setRawHeader(it.key(), it.value());
switch( verb )
{
case HttpVerb::Get:
@@ -206,11 +250,34 @@ void BaseJob::gotReply()
{
setStatus(checkReply(d->reply.data()));
if (status().good())
- setStatus(parseReply(d->reply->readAll()));
+ setStatus(parseReply(d->reply.data()));
finishJob();
}
+bool checkContentType(const QByteArray& type, const QByteArrayList& patterns)
+{
+ if (patterns.isEmpty())
+ return true;
+
+ for (const auto& pattern: patterns)
+ {
+ if (pattern.startsWith('*') || type == pattern) // Fast lane
+ return true;
+
+ auto patternParts = pattern.split('/');
+ Q_ASSERT_X(patternParts.size() <= 2, __FUNCTION__,
+ "BaseJob: Expected content type should have up to two"
+ " /-separated parts; violating pattern: " + pattern);
+
+ if (type.split('/').front() == patternParts.front() &&
+ patternParts.back() == "*")
+ return true; // Exact match already went on fast lane
+ }
+
+ return false;
+}
+
BaseJob::Status BaseJob::checkReply(QNetworkReply* reply) const
{
qCDebug(d->logCat) << this << "returned from" << reply->url().toDisplayString();
@@ -218,30 +285,35 @@ BaseJob::Status BaseJob::checkReply(QNetworkReply* reply) const
qCDebug(d->logCat) << this << "returned" << reply->error();
switch( reply->error() )
{
- case QNetworkReply::NoError:
- return NoError;
-
- case QNetworkReply::AuthenticationRequiredError:
- case QNetworkReply::ContentAccessDenied:
- case QNetworkReply::ContentOperationNotPermittedError:
- return { ContentAccessError, reply->errorString() };
-
- case QNetworkReply::ProtocolInvalidOperationError:
- case QNetworkReply::UnknownContentError:
- return { IncorrectRequestError, reply->errorString() };
-
- case QNetworkReply::ContentNotFoundError:
- return { NotFoundError, reply->errorString() };
-
- default:
- return { NetworkError, reply->errorString() };
+ case QNetworkReply::NoError:
+ if (checkContentType(reply->rawHeader("Content-Type"),
+ d->expectedContentTypes))
+ return NoError;
+ else
+ return { IncorrectResponseError,
+ "Incorrect content type of the response" };
+
+ case QNetworkReply::AuthenticationRequiredError:
+ case QNetworkReply::ContentAccessDenied:
+ case QNetworkReply::ContentOperationNotPermittedError:
+ return { ContentAccessError, reply->errorString() };
+
+ case QNetworkReply::ProtocolInvalidOperationError:
+ case QNetworkReply::UnknownContentError:
+ return { IncorrectRequestError, reply->errorString() };
+
+ case QNetworkReply::ContentNotFoundError:
+ return { NotFoundError, reply->errorString() };
+
+ default:
+ return { NetworkError, reply->errorString() };
}
}
-BaseJob::Status BaseJob::parseReply(QByteArray data)
+BaseJob::Status BaseJob::parseReply(QNetworkReply* reply)
{
QJsonParseError error;
- QJsonDocument json = QJsonDocument::fromJson(data, &error);
+ QJsonDocument json = QJsonDocument::fromJson(reply->readAll(), &error);
if( error.error == QJsonParseError::NoError )
return parseJson(json);
else