aboutsummaryrefslogtreecommitdiff
path: root/jobs
diff options
context:
space:
mode:
authorKitsune Ral <Kitsune-Ral@users.sf.net>2018-01-03 12:19:25 +0900
committerKitsune Ral <Kitsune-Ral@users.sf.net>2018-01-09 11:54:38 +0900
commit986fc27e451b21cdd118e74da9e9ff22e275ef75 (patch)
tree18895750e768cb773ec754176242bf947bbfc558 /jobs
parentf951ab8a931c68f61b9710a540b2c971bbf502ca (diff)
downloadlibquotient-986fc27e451b21cdd118e74da9e9ff22e275ef75.tar.gz
libquotient-986fc27e451b21cdd118e74da9e9ff22e275ef75.zip
BaseJob, MediaThumbnailJob: Support request and response headers
Enable specifying headers in the request and checking/using headers in the response.
Diffstat (limited to 'jobs')
-rw-r--r--jobs/basejob.cpp110
-rw-r--r--jobs/basejob.h18
-rw-r--r--jobs/mediathumbnailjob.cpp6
-rw-r--r--jobs/mediathumbnailjob.h2
4 files changed, 107 insertions, 29 deletions
diff --git a/jobs/basejob.cpp b/jobs/basejob.cpp
index 9df3e430..980814c4 100644
--- a/jobs/basejob.cpp
+++ b/jobs/basejob.cpp
@@ -60,10 +60,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;
@@ -117,6 +123,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 +159,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 +185,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 +246,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 +281,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
diff --git a/jobs/basejob.h b/jobs/basejob.h
index 2f75c095..1d7f2388 100644
--- a/jobs/basejob.h
+++ b/jobs/basejob.h
@@ -25,7 +25,6 @@
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <QtCore/QUrlQuery>
-#include <QtCore/QScopedPointer>
class QNetworkReply;
class QSslError;
@@ -59,6 +58,7 @@ namespace QMatrixClient
, ContentAccessError
, NotFoundError
, IncorrectRequestError
+ , IncorrectResponseError
, UserDefinedError = 200
};
@@ -214,12 +214,21 @@ namespace QMatrixClient
void failure(BaseJob*);
protected:
+ using headers_t = QHash<QByteArray, QByteArray>;
+
const QString& apiEndpoint() const;
void setApiEndpoint(const QString& apiEndpoint);
+ const headers_t& requestHeaders() const;
+ void setRequestHeader(const headers_t::key_type& headerName,
+ const headers_t::mapped_type& headerValue);
+ void setRequestHeaders(const headers_t& headers);
const QUrlQuery& query() const;
void setRequestQuery(const QUrlQuery& query);
const Data& requestData() const;
void setRequestData(const Data& data);
+ const QByteArrayList& expectedContentTypes() const;
+ void addExpectedContentType(const QByteArray& contentType);
+ void setExpectedContentTypes(const QByteArrayList& contentTypes);
virtual void beforeStart(const ConnectionData* connData);
@@ -240,11 +249,11 @@ namespace QMatrixClient
* Processes the reply. By default, parses the reply into
* a QJsonDocument and calls parseJson() if it's a valid JSON.
*
- * @param data raw contents of a HTTP reply from the server (without headers)
+ * @param reply raw contents of a HTTP reply from the server (without headers)
*
* @see gotReply, parseJson
*/
- virtual Status parseReply(QByteArray data);
+ virtual Status parseReply(QNetworkReply* reply);
/**
* Processes the JSON document received from the Matrix server.
@@ -265,7 +274,8 @@ namespace QMatrixClient
void setLoggingCategory(LoggingCategory lcf);
// Job objects should only be deleted via QObject::deleteLater
- virtual ~BaseJob();
+ ~BaseJob() override;
+
protected slots:
void timeout();
diff --git a/jobs/mediathumbnailjob.cpp b/jobs/mediathumbnailjob.cpp
index c0d67a63..9337549e 100644
--- a/jobs/mediathumbnailjob.cpp
+++ b/jobs/mediathumbnailjob.cpp
@@ -17,8 +17,8 @@
*/
#include "mediathumbnailjob.h"
-#include "util.h"
+#include <QtNetwork/QNetworkReply>
#include <QtCore/QDebug>
using namespace QMatrixClient;
@@ -47,9 +47,9 @@ QImage MediaThumbnailJob::scaledThumbnail(QSize toSize) const
Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
-BaseJob::Status MediaThumbnailJob::parseReply(QByteArray data)
+BaseJob::Status MediaThumbnailJob::parseReply(QNetworkReply* reply)
{
- if( !_thumbnail.loadFromData(data) )
+ if( !_thumbnail.loadFromData(reply->readAll()) )
{
qCDebug(JOBS) << "MediaThumbnailJob: could not read image data";
}
diff --git a/jobs/mediathumbnailjob.h b/jobs/mediathumbnailjob.h
index f8f36fe9..2d6853c7 100644
--- a/jobs/mediathumbnailjob.h
+++ b/jobs/mediathumbnailjob.h
@@ -36,7 +36,7 @@ namespace QMatrixClient
QImage scaledThumbnail(QSize toSize) const;
protected:
- Status parseReply(QByteArray data) override;
+ Status parseReply(QNetworkReply* reply) override;
private:
QImage _thumbnail;