diff options
author | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2018-01-03 12:19:25 +0900 |
---|---|---|
committer | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2018-01-09 11:54:38 +0900 |
commit | 986fc27e451b21cdd118e74da9e9ff22e275ef75 (patch) | |
tree | 18895750e768cb773ec754176242bf947bbfc558 /jobs | |
parent | f951ab8a931c68f61b9710a540b2c971bbf502ca (diff) | |
download | libquotient-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.cpp | 110 | ||||
-rw-r--r-- | jobs/basejob.h | 18 | ||||
-rw-r--r-- | jobs/mediathumbnailjob.cpp | 6 | ||||
-rw-r--r-- | jobs/mediathumbnailjob.h | 2 |
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; |