From 5937127b73a82fc86f6546397373ce9dbaf4e560 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 19 Nov 2019 17:57:41 +0900 Subject: BaseJob: Don't send accessToken if not needed; send again on 401 The first part closes #358; the second part is a workaround for non-standard cases when endpoints without security by the spec turn out to be secured (in particular, the case of authenticating media servers). --- lib/jobs/basejob.cpp | 66 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 26 deletions(-) (limited to 'lib/jobs/basejob.cpp') diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 13e65188..7f558685 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -226,8 +226,9 @@ void BaseJob::Private::sendRequest() requestQuery) }; if (!requestHeaders.contains("Content-Type")) req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - req.setRawHeader("Authorization", - QByteArray("Bearer ") + connection->accessToken()); + if (needsToken) + req.setRawHeader("Authorization", + QByteArray("Bearer ") + connection->accessToken()); req.setAttribute(QNetworkRequest::BackgroundRequestAttribute, inBackground); req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); req.setMaximumRedirectsAllowed(10); @@ -274,6 +275,7 @@ void BaseJob::sendRequest() return; Q_ASSERT(d->connection && status().code == Pending); qCDebug(d->logCat).noquote() << "Making" << d->dumpRequest(); + d->needsToken |= d->connection->needsToken(objectName()); emit aboutToSendRequest(); d->sendRequest(); Q_ASSERT(d->reply); @@ -341,32 +343,32 @@ bool checkContentType(const QByteArray& type, const QByteArrayList& patterns) return false; } -BaseJob::Status BaseJob::Status::fromHttpCode(int httpCode, QString msg) +BaseJob::StatusCode BaseJob::Status::fromHttpCode(int httpCode) { + if (httpCode / 10 == 41) // 41x errors + return httpCode == 410 ? IncorrectRequestError : NotFoundError; + switch (httpCode) { + case 401: + return Unauthorised; // clang-format off - return { [httpCode]() -> StatusCode { - if (httpCode / 10 == 41) // 41x errors - return httpCode == 410 ? IncorrectRequestError : NotFoundError; - switch (httpCode) { - case 401: case 403: case 407: - return ContentAccessError; - case 404: - return NotFoundError; - case 400: case 405: case 406: case 426: case 428: case 505: - case 494: // Unofficial nginx "Request header too large" - case 497: // Unofficial nginx "HTTP request sent to HTTPS port" - return IncorrectRequestError; - case 429: - return TooManyRequestsError; - case 501: case 510: - return RequestNotImplementedError; - case 511: - return NetworkAuthRequiredError; - default: - return NetworkError; - } - }(), std::move(msg) }; - // clang-format on + case 403: case 407: // clang-format on + return ContentAccessError; + case 404: + return NotFoundError; + // clang-format off + case 400: case 405: case 406: case 426: case 428: case 505: // clang-format on + case 494: // Unofficial nginx "Request header too large" + case 497: // Unofficial nginx "HTTP request sent to HTTPS port" + return IncorrectRequestError; + case 429: + return TooManyRequestsError; + case 501: case 510: + return RequestNotImplementedError; + case 511: + return NetworkAuthRequiredError; + default: + return NetworkError; + } } QDebug BaseJob::Status::dumpToLog(QDebug dbg) const @@ -496,6 +498,16 @@ void BaseJob::finishJob() d->connection->submit(this); return; } + if (error() == Unauthorised && !d->needsToken + && !d->connection->accessToken().isEmpty()) { + // Rerun with access token (extension of the spec while + // https://github.com/matrix-org/matrix-doc/issues/701 is pending) + d->connection->setNeedsToken(objectName()); + qCWarning(d->logCat) << this << "re-running with authentication"; + emit retryScheduled(d->retriesTaken, 0); + setStatus(Pending); + sendRequest(); + } if ((error() == NetworkError || error() == Timeout) && d->retriesTaken < d->maxRetries) { // TODO: The whole retrying thing should be put to Connection(Manager) @@ -596,6 +608,8 @@ QString BaseJob::statusCaption() const return tr("Network problems"); case TimeoutError: return tr("Request timed out"); + case Unauthorised: + return tr("Unauthorised request"); case ContentAccessError: return tr("Access error"); case NotFoundError: -- cgit v1.2.3 From f6505a541fc0a2e02f9c79496488121a3e46fb01 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 12 Dec 2019 19:47:07 +0300 Subject: BaseJob: prepare() -> initiate() + refactoring around it * BaseJob::initiate() now calls ConnectionData::submit() without relying on Connection to do that * ConnectionData::submit() is now the only site where a job enters Pending state * No more shortcuts to BaseJob::sendRequest(), even retries are sent through the ConnectionData submission queue * Additional validation in BaseJob::initiate() that the request data device is actually open (because QtNetwork API officially requires that, even if you can get away passing a closed QBuffer to it) --- lib/jobs/basejob.cpp | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) (limited to 'lib/jobs/basejob.cpp') diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 7f558685..4653e58a 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -138,8 +138,7 @@ BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, setObjectName(name); connect(&d->timer, &QTimer::timeout, this, &BaseJob::timeout); connect(&d->retryTimer, &QTimer::timeout, this, [this] { - setStatus(Pending); - sendRequest(); + d->connection->submit(this); }); } @@ -259,14 +258,27 @@ void BaseJob::onSentRequest(QNetworkReply*) {} void BaseJob::beforeAbandon(QNetworkReply*) {} -void BaseJob::prepare(ConnectionData* connData, bool inBackground) +void BaseJob::initiate(ConnectionData* connData, bool inBackground) { + Q_ASSERT(connData != nullptr); + d->inBackground = inBackground; d->connection = connData; doPrepare(); - if (status().code != Unprepared && status().code != Pending) + + if ((d->verb == HttpVerb::Post || d->verb == HttpVerb::Put) + && !d->requestData.source()->isReadable()) { + setStatus(FileError, "Request data not ready"); + } + Q_ASSERT(status().code != Pending); // doPrepare() must NOT set this + if (status().code == Unprepared) { + d->connection->submit(this); + } else { + qDebug(d->logCat).noquote() + << "Request failed preparation and won't be sent:" + << d->dumpRequest(); QTimer::singleShot(0, this, &BaseJob::finishJob); - setStatus(Pending); + } } void BaseJob::sendRequest() @@ -292,7 +304,7 @@ void BaseJob::sendRequest() onSentRequest(d->reply.data()); emit sentRequest(); } else - qCWarning(d->logCat).noquote() + qCCritical(d->logCat).noquote() << "Request could not start:" << d->dumpRequest(); } @@ -494,7 +506,6 @@ void BaseJob::finishJob() stop(); if (error() == TooManyRequests) { emit rateLimited(); - setStatus(Pending); d->connection->submit(this); return; } @@ -505,8 +516,7 @@ void BaseJob::finishJob() d->connection->setNeedsToken(objectName()); qCWarning(d->logCat) << this << "re-running with authentication"; emit retryScheduled(d->retriesTaken, 0); - setStatus(Pending); - sendRequest(); + d->connection->submit(this); } if ((error() == NetworkError || error() == Timeout) && d->retriesTaken < d->maxRetries) { -- cgit v1.2.3 From 38f45066a2304935550e2c5b6be21a9744f66bf1 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 12 Dec 2019 20:57:41 +0300 Subject: BaseJob::initiate(): fix nullptr dereferencing --- lib/jobs/basejob.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/jobs/basejob.cpp') diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 4653e58a..68adeaf6 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -267,7 +267,7 @@ void BaseJob::initiate(ConnectionData* connData, bool inBackground) doPrepare(); if ((d->verb == HttpVerb::Post || d->verb == HttpVerb::Put) - && !d->requestData.source()->isReadable()) { + && d->requestData.source() && !d->requestData.source()->isReadable()) { setStatus(FileError, "Request data not ready"); } Q_ASSERT(status().code != Pending); // doPrepare() must NOT set this -- cgit v1.2.3