diff options
author | Alexey Rusakov <Kitsune-Ral@users.sf.net> | 2021-10-04 18:39:21 +0200 |
---|---|---|
committer | Alexey Rusakov <Kitsune-Ral@users.sf.net> | 2021-10-04 18:39:55 +0200 |
commit | 96f31d7d8ed1c9ab905c24ac039079aea622f4dc (patch) | |
tree | 06fea86951340d25a02cdd6f7eb89164be814bd9 /lib/jobs | |
parent | 7c11f7fddbcc98e4b3b92060c475799d7518624c (diff) | |
download | libquotient-96f31d7d8ed1c9ab905c24ac039079aea622f4dc.tar.gz libquotient-96f31d7d8ed1c9ab905c24ac039079aea622f4dc.zip |
BaseJob: percent-encode variable path parts
This is meant to spare clients from having to percent-encode
room aliases, v3 event ids etc. that happen to hit the endpoint path.
It is unfair to expect clients to do that since they are not supposed
to care about the shape of CS API, which parameter should be encoded
in which way. The trick (together with the slightly updated GTAD
configuration) is to percent-encode parts that happen to be QStrings
and not `const char[]`'s while passing all constant parts as plain
C character literals. This also allows to make it more certain that
the path is correctly encoded by passing and storing QByteArray's
wherever the path is already encoded, and only use QStrings (next to
const char arrays) before that.
Since the change alters the API contract (even if that contract was
crappy), some crude detection of percent-encoded stuff on input is
inserted; if input is already percent-encoded, a warning is put to
the logs, alerting developers about the change.
Diffstat (limited to 'lib/jobs')
-rw-r--r-- | lib/jobs/basejob.cpp | 51 | ||||
-rw-r--r-- | lib/jobs/basejob.h | 21 | ||||
-rw-r--r-- | lib/jobs/syncjob.cpp | 2 |
3 files changed, 54 insertions, 20 deletions
diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 85066024..73762e4f 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -71,7 +71,7 @@ public: // Using an idiom from clang-tidy: // http://clang.llvm.org/extra/clang-tidy/checks/modernize-pass-by-value.html - Private(HttpVerb v, QString endpoint, const QUrlQuery& q, + Private(HttpVerb v, QByteArray endpoint, const QUrlQuery& q, RequestData&& data, bool nt) : verb(v) , apiEndpoint(std::move(endpoint)) @@ -106,7 +106,7 @@ public: // Contents for the network request HttpVerb verb; - QString apiEndpoint; + QByteArray apiEndpoint; QHash<QByteArray, QByteArray> requestHeaders; QUrlQuery requestQuery; RequestData requestData; @@ -166,14 +166,36 @@ public: } }; -BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, +inline bool isHex(QChar c) +{ + return c.isDigit() || (c >= u'A' && c <= u'F') || (c >= u'a' && c <= u'f'); +} + +QByteArray BaseJob::encodeIfParam(const QString& paramPart) +{ + const auto percentIndex = paramPart.indexOf('%'); + if (percentIndex != -1 && paramPart.size() > percentIndex + 2 + && isHex(paramPart[percentIndex + 1]) + && isHex(paramPart[percentIndex + 2])) { + qCWarning(JOBS) + << "Developers, upfront percent-encoding of job parameters is " + "deprecated since libQuotient 0.7; the string involved is" + << paramPart; + return QUrl(paramPart, QUrl::TolerantMode).toEncoded(); + } + return QUrl::toPercentEncoding(paramPart); +} + +BaseJob::BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint, bool needsToken) - : BaseJob(verb, name, endpoint, QUrlQuery {}, RequestData {}, needsToken) + : BaseJob(verb, name, std::move(endpoint), QUrlQuery {}, RequestData {}, + needsToken) {} -BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, +BaseJob::BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint, const QUrlQuery& query, RequestData&& data, bool needsToken) - : d(new Private(verb, endpoint, query, std::move(data), needsToken)) + : d(new Private(verb, std::move(endpoint), query, std::move(data), + needsToken)) { setObjectName(name); connect(&d->timer, &QTimer::timeout, this, &BaseJob::timeout); @@ -194,13 +216,6 @@ QUrl BaseJob::requestUrl() const { return d->reply ? d->reply->url() : QUrl(); } bool BaseJob::isBackground() const { return d->inBackground; } -//const QString& BaseJob::apiEndpoint() const { return d->apiUrl.path(); } - -//void BaseJob::setApiEndpoint(const QString& apiEndpoint) -//{ -// d->apiEndpoint = apiEndpoint; -//} - const BaseJob::headers_t& BaseJob::requestHeaders() const { return d->requestHeaders; @@ -259,13 +274,17 @@ const QNetworkReply* BaseJob::reply() const { return d->reply.data(); } QNetworkReply* BaseJob::reply() { return d->reply.data(); } -QUrl BaseJob::makeRequestUrl(QUrl baseUrl, const QString& path, +QUrl BaseJob::makeRequestUrl(QUrl baseUrl, const QByteArray& encodedPath, const QUrlQuery& query) { // Make sure the added path is relative even if it's not (the official // API definitions have the leading slash though it's not really correct). - baseUrl = baseUrl.resolved( - QUrl(path.mid(path.startsWith('/')), QUrl::TolerantMode)); + const auto pathUrl = + QUrl::fromEncoded(encodedPath.mid(encodedPath.startsWith('/')), + QUrl::StrictMode); + Q_ASSERT_X(pathUrl.isValid(), __FUNCTION__, + qPrintable(pathUrl.errorString())); + baseUrl = baseUrl.resolved(pathUrl); baseUrl.setQuery(query); return baseUrl; } diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 663c121c..81455307 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -9,6 +9,7 @@ #include "../converters.h" #include <QtCore/QObject> +#include <QtCore/QStringBuilder> class QNetworkReply; class QSslError; @@ -23,6 +24,14 @@ class BaseJob : public QObject { Q_PROPERTY(QUrl requestUrl READ requestUrl CONSTANT) Q_PROPERTY(int maxRetries READ maxRetries WRITE setMaxRetries) Q_PROPERTY(int statusCode READ error NOTIFY statusChanged) + + static QByteArray encodeIfParam(const QString& paramPart); + template <int N> + static inline auto encodeIfParam(const char (&constPart)[N]) + { + return constPart; + } + public: /*! The status code of a job * @@ -70,6 +79,12 @@ public: }; Q_ENUM(StatusCode) + template <typename... StrTs> + static QByteArray makePath(StrTs&&... parts) + { + return (QByteArray() % ... % encodeIfParam(parts)); + } + using Data #ifndef Q_CC_MSVC Q_DECL_DEPRECATED_X("Use Quotient::RequestData instead") @@ -124,9 +139,9 @@ public: }; public: - BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, + BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint, bool needsToken = true); - BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, + BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint, const QUrlQuery& query, RequestData&& data = {}, bool needsToken = true); @@ -352,7 +367,7 @@ protected: * The function ensures exactly one '/' between the path component of * \p baseUrl and \p path. The query component of \p baseUrl is ignored. */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& path, + static QUrl makeRequestUrl(QUrl baseUrl, const QByteArray &encodedPath, const QUrlQuery& query = {}); /*! Prepares the job for execution diff --git a/lib/jobs/syncjob.cpp b/lib/jobs/syncjob.cpp index 59a34ef3..9b1b46f0 100644 --- a/lib/jobs/syncjob.cpp +++ b/lib/jobs/syncjob.cpp @@ -10,7 +10,7 @@ static size_t jobId = 0; SyncJob::SyncJob(const QString& since, const QString& filter, int timeout, const QString& presence) : BaseJob(HttpVerb::Get, QStringLiteral("SyncJob-%1").arg(++jobId), - QStringLiteral("_matrix/client/r0/sync")) + "_matrix/client/r0/sync") { setLoggingCategory(SYNCJOB); QUrlQuery query; |