aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAlexey Rusakov <Kitsune-Ral@users.sf.net>2021-10-04 18:39:21 +0200
committerAlexey Rusakov <Kitsune-Ral@users.sf.net>2021-10-04 18:39:55 +0200
commit96f31d7d8ed1c9ab905c24ac039079aea622f4dc (patch)
tree06fea86951340d25a02cdd6f7eb89164be814bd9 /lib
parent7c11f7fddbcc98e4b3b92060c475799d7518624c (diff)
downloadlibquotient-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')
-rw-r--r--lib/jobs/basejob.cpp51
-rw-r--r--lib/jobs/basejob.h21
-rw-r--r--lib/jobs/syncjob.cpp2
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;