aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/connection.cpp41
-rw-r--r--lib/connection.h6
-rw-r--r--lib/encryptionmanager.cpp223
-rw-r--r--lib/encryptionmanager.h32
-rw-r--r--lib/events/encryptionevent.cpp53
-rw-r--r--lib/events/encryptionevent.h78
-rw-r--r--lib/events/event.h6
-rw-r--r--lib/events/eventcontent.h8
-rw-r--r--lib/events/eventloader.h20
-rw-r--r--lib/events/roomevent.cpp12
-rw-r--r--lib/events/roomevent.h3
-rw-r--r--lib/events/roommemberevent.h10
-rw-r--r--lib/events/simplestateevents.h26
-rw-r--r--lib/events/stateevent.cpp8
-rw-r--r--lib/events/stateevent.h21
-rw-r--r--lib/joinstate.h2
-rw-r--r--lib/logging.cpp12
-rw-r--r--lib/room.cpp100
-rw-r--r--lib/room.h30
-rw-r--r--lib/settings.cpp22
-rw-r--r--lib/settings.h5
-rw-r--r--lib/user.cpp2
-rw-r--r--lib/util.cpp14
-rw-r--r--lib/util.h8
24 files changed, 638 insertions, 104 deletions
diff --git a/lib/connection.cpp b/lib/connection.cpp
index 511a0411..79411dce 100644
--- a/lib/connection.cpp
+++ b/lib/connection.cpp
@@ -23,6 +23,7 @@
#include "events/eventloader.h"
#include "room.h"
#include "settings.h"
+#include "encryptionmanager.h"
#include "csapi/login.h"
#include "csapi/capabilities.h"
#include "csapi/logout.h"
@@ -104,6 +105,8 @@ class Connection::Private
GetCapabilitiesJob* capabilitiesJob = nullptr;
GetCapabilitiesJob::Capabilities capabilities;
+ QScopedPointer<EncryptionManager> encryptionManager;
+
SyncJob* syncJob = nullptr;
bool cacheState = true;
@@ -161,33 +164,11 @@ Connection::~Connection()
stopSync();
}
-static const auto ServerPartRegEx = QStringLiteral(
- "(\\[[^]]+\\]|[^:@]+)" // Either IPv6 address or hostname/IPv4 address
- "(?::(\\d{1,5}))?" // Optional port
-);
-
-QString serverPart(const QString& mxId)
-{
- static auto re = "^[@!#$+].+?:(" // Localpart and colon
- % ServerPartRegEx % ")$";
- static QRegularExpression parser(re,
- QRegularExpression::UseUnicodePropertiesOption); // Because Asian digits
- return parser.match(mxId).captured(1);
-}
-
-void Connection::resolveServer(const QString& mxidOrDomain)
+void Connection::resolveServer(const QString& mxid)
{
- // mxIdOrDomain may be something as complex as
- // @username:[IPv6:address]:port, or as simple as a plain serverpart.
- static QRegularExpression parser(
- "^(@.+?:)?" // Optional username (allow everything for compatibility)
- % ServerPartRegEx % '$',
- QRegularExpression::UseUnicodePropertiesOption); // Because Asian digits
- auto match = parser.match(mxidOrDomain);
-
- auto maybeBaseUrl = QUrl::fromUserInput(match.captured(2));
+ auto maybeBaseUrl = QUrl::fromUserInput(serverPart(mxid));
maybeBaseUrl.setScheme("https"); // Instead of the Qt-default "http"
- if (!match.hasMatch() || !maybeBaseUrl.isValid())
+ if (maybeBaseUrl.isEmpty() || !maybeBaseUrl.isValid())
{
emit resolveError(
tr("%1 is not a valid homeserver address")
@@ -258,6 +239,16 @@ void Connection::doConnectToServer(const QString& user, const QString& password,
[this, loginJob] {
d->connectWithToken(loginJob->userId(), loginJob->accessToken(),
loginJob->deviceId());
+
+ AccountSettings accountSettings(loginJob->userId());
+ d->encryptionManager.reset(new EncryptionManager(accountSettings.encryptionAccountPickle()));
+ if (accountSettings.encryptionAccountPickle().isEmpty()) {
+ accountSettings.setEncryptionAccountPickle(d->encryptionManager->olmAccountPickle());
+ }
+
+ d->encryptionManager->uploadIdentityKeys(this);
+ d->encryptionManager->uploadOneTimeKeys(this);
+
});
connect(loginJob, &BaseJob::failure, this,
[this, loginJob] {
diff --git a/lib/connection.h b/lib/connection.h
index eca3c5be..11499a6e 100644
--- a/lib/connection.h
+++ b/lib/connection.h
@@ -122,7 +122,7 @@ namespace QMatrixClient
explicit Connection(QObject* parent = nullptr);
explicit Connection(const QUrl& server, QObject* parent = nullptr);
- virtual ~Connection();
+ ~Connection() override;
/** Get all Invited and Joined rooms
* \return a hashmap from a composite key - room name and whether
@@ -408,8 +408,8 @@ namespace QMatrixClient
/** Set the homeserver base URL */
void setHomeserver(const QUrl& baseUrl);
- /** Determine and set the homeserver from domain or MXID */
- void resolveServer(const QString& mxidOrDomain);
+ /** Determine and set the homeserver from MXID */
+ void resolveServer(const QString& mxid);
void connectToServer(const QString& user, const QString& password,
const QString& initialDeviceName,
diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp
new file mode 100644
index 00000000..50db9889
--- /dev/null
+++ b/lib/encryptionmanager.cpp
@@ -0,0 +1,223 @@
+#include "encryptionmanager.h"
+
+#include <functional>
+#include <memory>
+#include <QtCore/QStringBuilder>
+#include <QtCore/QHash>
+#include <account.h> // QtOlm
+
+#include "csapi/keys.h"
+#include "connection.h"
+
+using namespace QMatrixClient;
+using namespace QtOlm;
+using std::move;
+
+static const auto ed25519Name = QStringLiteral("ed25519");
+static const auto Curve25519Name = QStringLiteral("curve25519");
+static const auto SignedCurve25519Name = QStringLiteral("signed_curve25519");
+static const auto OlmV1Curve25519AesSha2AlgoName = QStringLiteral("m.olm.v1.curve25519-aes-sha2");
+static const auto MegolmV1AesSha2AlgoName = QStringLiteral("m.megolm.v1.aes-sha2");
+static const QStringList SupportedAlgorithms = { OlmV1Curve25519AesSha2AlgoName, MegolmV1AesSha2AlgoName };
+
+class EncryptionManager::Private
+{
+ public:
+ explicit Private(const QByteArray& encryptionAccountPickle, float signedKeysProportion, float oneTimeKeyThreshold)
+ : signedKeysProportion(move(signedKeysProportion)),
+ oneTimeKeyThreshold(move(oneTimeKeyThreshold))
+ {
+ Q_ASSERT((0 <= signedKeysProportion) && (signedKeysProportion <= 1));
+ Q_ASSERT((0 <= oneTimeKeyThreshold) && (oneTimeKeyThreshold <= 1));
+ if (encryptionAccountPickle.isEmpty())
+ {
+ olmAccount.reset(new Account());
+ } else {
+ olmAccount.reset(new Account(encryptionAccountPickle)); // TODO: passphrase even with qtkeychain?
+ }
+ /*
+ * Note about targetKeysNumber:
+ *
+ * From: https://github.com/Zil0/matrix-python-sdk/
+ * File: matrix_client/crypto/olm_device.py
+ *
+ * Try to maintain half the number of one-time keys libolm can hold uploaded
+ * on the HS. This is because some keys will be claimed by peers but not
+ * used instantly, and we want them to stay in libolm, until the limit is reached
+ * and it starts discarding keys, starting by the oldest.
+ */
+ targetKeysNumber = olmAccount->maxOneTimeKeys(); // 2 // see note below
+ targetOneTimeKeyCounts =
+ {
+ {SignedCurve25519Name, qRound(signedKeysProportion * targetKeysNumber)},
+ {Curve25519Name, qRound((1-signedKeysProportion) * targetKeysNumber)}
+ };
+ }
+ ~Private() = default;
+
+ UploadKeysJob* uploadIdentityKeysJob = nullptr;
+ UploadKeysJob* uploadOneTimeKeysJob = nullptr;
+
+ QScopedPointer<Account> olmAccount;
+
+ float signedKeysProportion;
+ float oneTimeKeyThreshold;
+ int targetKeysNumber;
+
+ void updateKeysToUpload();
+ bool oneTimeKeyShouldUpload();
+
+ QHash<QString, int> oneTimeKeyCounts;
+ void setOneTimeKeyCounts(const QHash<QString, int> oneTimeKeyCountsNewValue)
+ {
+ oneTimeKeyCounts = oneTimeKeyCountsNewValue;
+ updateKeysToUpload();
+ }
+ QHash<QString, int> oneTimeKeysToUploadCounts;
+ QHash<QString, int> targetOneTimeKeyCounts;
+};
+
+EncryptionManager::EncryptionManager(const QByteArray &encryptionAccountPickle, float signedKeysProportion, float oneTimeKeyThreshold,
+ QObject* parent)
+ : QObject(parent),
+ d(std::make_unique<Private>(std::move(encryptionAccountPickle), std::move(signedKeysProportion), std::move(oneTimeKeyThreshold)))
+{
+
+}
+
+EncryptionManager::~EncryptionManager() = default;
+
+void EncryptionManager::uploadIdentityKeys(Connection* connection)
+{
+ // https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-keys-upload
+ DeviceKeys deviceKeys
+ {
+ /*
+ * The ID of the user the device belongs to. Must match the user ID used when logging in.
+ * The ID of the device these keys belong to. Must match the device ID used when logging in.
+ * The encryption algorithms supported by this device.
+ */
+ connection->userId(), connection->deviceId(), SupportedAlgorithms,
+ /*
+ * Public identity keys. The names of the properties should be in the format <algorithm>:<device_id>.
+ * The keys themselves should be encoded as specified by the key algorithm.
+ */
+ {
+ {
+ Curve25519Name + QStringLiteral(":") + connection->deviceId(),
+ d->olmAccount->curve25519IdentityKey()
+ },
+ {
+ ed25519Name + QStringLiteral(":") + connection->deviceId(),
+ d->olmAccount->ed25519IdentityKey()
+ }
+ },
+ /* signatures should be provided after the unsigned deviceKeys generation */
+ {}
+ };
+
+ QJsonObject deviceKeysJsonObject = toJson(deviceKeys);
+ /* additionally removing signatures key,
+ * since we could not initialize deviceKeys
+ * without an empty signatures value:
+ */
+ deviceKeysJsonObject.remove(QStringLiteral("signatures"));
+ /*
+ * Signatures for the device key object.
+ * A map from user ID, to a map from <algorithm>:<device_id> to the signature.
+ * The signature is calculated using the process called Signing JSON.
+ */
+ deviceKeys.signatures =
+ {
+ {
+ connection->userId(),
+ {
+ {
+ ed25519Name + QStringLiteral(":") + connection->deviceId(),
+ d->olmAccount->sign(deviceKeysJsonObject)
+ }
+ }
+ }
+ };
+
+ connect(d->uploadIdentityKeysJob, &BaseJob::success, this, [this] {
+ d->setOneTimeKeyCounts(d->uploadIdentityKeysJob->oneTimeKeyCounts());
+ qDebug() << QString("Uploaded identity keys.");
+ });
+ d->uploadIdentityKeysJob = connection->callApi<UploadKeysJob>(deviceKeys);
+}
+
+void EncryptionManager::uploadOneTimeKeys(Connection* connection, bool forceUpdate)
+{
+ if (forceUpdate || d->oneTimeKeyCounts.isEmpty())
+ {
+ auto job = connection->callApi<UploadKeysJob>();
+ connect(job, &BaseJob::success, this, [job,this] {
+ d->setOneTimeKeyCounts(job->oneTimeKeyCounts());
+ });
+
+ }
+
+ int signedKeysToUploadCount = d->oneTimeKeysToUploadCounts.value(SignedCurve25519Name, 0);
+ int unsignedKeysToUploadCount = d->oneTimeKeysToUploadCounts.value(Curve25519Name, 0);
+
+ d->olmAccount->generateOneTimeKeys(signedKeysToUploadCount + unsignedKeysToUploadCount);
+
+ QHash<QString, QVariant> oneTimeKeys = {};
+ const auto& olmAccountCurve25519OneTimeKeys = d->olmAccount->curve25519OneTimeKeys();
+
+ int oneTimeKeysCounter = 0;
+ for (auto it = olmAccountCurve25519OneTimeKeys.cbegin(); it != olmAccountCurve25519OneTimeKeys.cend(); ++it)
+ {
+ QString keyId = it.key();
+ QString keyType;
+ QVariant key;
+ if (oneTimeKeysCounter < signedKeysToUploadCount)
+ {
+ QJsonObject message
+ {
+ {QStringLiteral("key"), it.value().toString()}
+ };
+ key = d->olmAccount->sign(message);
+ keyType = SignedCurve25519Name;
+
+ } else {
+ key = it.value();
+ keyType = Curve25519Name;
+ }
+ ++oneTimeKeysCounter;
+ oneTimeKeys.insert(QString("%1:%2").arg(keyType).arg(keyId), key);
+ }
+
+ d->uploadOneTimeKeysJob = connection->callApi<UploadKeysJob>(none, oneTimeKeys);
+ d->olmAccount->markKeysAsPublished();
+ qDebug() << QString("Uploaded new one-time keys: %1 signed, %2 unsigned.")
+ .arg(signedKeysToUploadCount).arg(unsignedKeysToUploadCount);
+}
+
+QByteArray EncryptionManager::olmAccountPickle()
+{
+ return d->olmAccount->pickle(); // TODO: passphrase even with qtkeychain?
+}
+
+void EncryptionManager::Private::updateKeysToUpload()
+{
+ for (auto it = targetOneTimeKeyCounts.cbegin(); it != targetOneTimeKeyCounts.cend(); ++it)
+ {
+ int numKeys = oneTimeKeyCounts.value(it.key(), 0);
+ int numToCreate = qMax(it.value() - numKeys, 0);
+ oneTimeKeysToUploadCounts.insert(it.key(), numToCreate);
+ }
+}
+
+bool EncryptionManager::Private::oneTimeKeyShouldUpload()
+{
+ if (oneTimeKeyCounts.empty())
+ return true;
+ for (auto it = targetOneTimeKeyCounts.cbegin(); it != targetOneTimeKeyCounts.cend(); ++it)
+ {
+ if (oneTimeKeyCounts.value(it.key(), 0) < it.value() * oneTimeKeyThreshold)
+ return true;
+ }
+ return false;
+}
diff --git a/lib/encryptionmanager.h b/lib/encryptionmanager.h
new file mode 100644
index 00000000..40fe7383
--- /dev/null
+++ b/lib/encryptionmanager.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <QtCore/QObject>
+
+namespace QMatrixClient
+{
+ class Connection;
+
+ class EncryptionManager: public QObject
+ {
+ Q_OBJECT
+
+ public:
+ // TODO: store constats separately?
+ // TODO: 0.5 oneTimeKeyThreshold instead of 0.1?
+ explicit EncryptionManager(const QByteArray& encryptionAccountPickle = QByteArray(), float signedKeysProportion = 1, float oneTimeKeyThreshold = float(0.1),
+ QObject* parent = nullptr);
+ ~EncryptionManager();
+
+ void uploadIdentityKeys(Connection* connection);
+ void uploadOneTimeKeys(Connection* connection, bool forceUpdate = false);
+ QByteArray olmAccountPickle();
+
+ private:
+ class Private;
+ std::unique_ptr<Private> d;
+
+ };
+
+} // namespace QMatrixClient
diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp
new file mode 100644
index 00000000..b8e2b575
--- /dev/null
+++ b/lib/events/encryptionevent.cpp
@@ -0,0 +1,53 @@
+//
+// Created by rusakov on 26/09/2017.
+// Contributed by andreev on 27/06/2019.
+//
+
+#include "encryptionevent.h"
+
+#include "converters.h"
+#include "logging.h"
+
+#include <array>
+
+static const std::array<QString, 1> encryptionStrings = { {
+ QStringLiteral("m.megolm.v1.aes-sha2")
+} };
+
+namespace QMatrixClient {
+ template <>
+ struct JsonConverter<EncryptionType>
+ {
+ static EncryptionType load(const QJsonValue& jv)
+ {
+ const auto& encryptionString = jv.toString();
+ for (auto it = encryptionStrings.begin();
+ it != encryptionStrings.end(); ++it)
+ if (encryptionString == *it)
+ return EncryptionType(it - encryptionStrings.begin());
+
+ qCWarning(EVENTS) << "Unknown EncryptionType: " << encryptionString;
+ return EncryptionType::Undefined;
+ }
+ };
+}
+
+using namespace QMatrixClient;
+
+EncryptionEventContent::EncryptionEventContent(const QJsonObject& json)
+ : encryption(fromJson<EncryptionType>(json["algorithm"_ls]))
+ , algorithm(sanitized(json["algorithm"_ls].toString()))
+ , rotationPeriodMs(json["rotation_period_ms"_ls].toInt(604800000))
+ , rotationPeriodMsgs(json["rotation_period_msgs"_ls].toInt(100))
+{ }
+
+void EncryptionEventContent::fillJson(QJsonObject* o) const
+{
+ Q_ASSERT(o);
+ Q_ASSERT_X(encryption != EncryptionType::Undefined, __FUNCTION__,
+ "The key 'algorithm' must be explicit in EncryptionEventContent");
+ if (encryption != EncryptionType::Undefined)
+ o->insert(QStringLiteral("algorithm"), algorithm);
+ o->insert(QStringLiteral("rotation_period_ms"), rotationPeriodMs);
+ o->insert(QStringLiteral("rotation_period_msgs"), rotationPeriodMsgs);
+}
diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h
new file mode 100644
index 00000000..b9e108f0
--- /dev/null
+++ b/lib/events/encryptionevent.h
@@ -0,0 +1,78 @@
+/******************************************************************************
+ * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "stateevent.h"
+#include "eventcontent.h"
+
+namespace QMatrixClient
+{
+ class EncryptionEventContent: public EventContent::Base
+ {
+ public:
+ enum EncryptionType : size_t { MegolmV1AesSha2 = 0,
+ Undefined };
+
+ explicit EncryptionEventContent(EncryptionType et = Undefined)
+ : encryption(et)
+ { }
+ explicit EncryptionEventContent(const QJsonObject& json);
+
+ EncryptionType encryption;
+ QString algorithm;
+ int rotationPeriodMs;
+ int rotationPeriodMsgs;
+
+ protected:
+ void fillJson(QJsonObject* o) const override;
+ };
+
+ using EncryptionType = EncryptionEventContent::EncryptionType;
+
+ class EncryptionEvent : public StateEvent<EncryptionEventContent>
+ {
+ Q_GADGET
+ public:
+ DEFINE_EVENT_TYPEID("m.room.encryption", EncryptionEvent)
+
+ using EncryptionType = EncryptionEventContent::EncryptionType;
+
+ explicit EncryptionEvent(const QJsonObject& obj = {}) // TODO: apropriate default value
+ : StateEvent(typeId(), obj)
+ { }
+ template <typename... ArgTs>
+ EncryptionEvent(ArgTs&&... contentArgs)
+ : StateEvent(typeId(), matrixTypeId(), QString(),
+ std::forward<ArgTs>(contentArgs)...)
+ { }
+
+ EncryptionType encryption() const { return content().encryption; }
+
+ QString algorithm() const { return content().algorithm; }
+ int rotationPeriodMs() const { return content().rotationPeriodMs; }
+ int rotationPeriodMsgs() const { return content().rotationPeriodMsgs; }
+
+ private:
+ REGISTER_ENUM(EncryptionType)
+ };
+
+ REGISTER_EVENT_TYPE(EncryptionEvent)
+ DEFINE_EVENTTYPE_ALIAS(Encryption, EncryptionEvent)
+} // namespace QMatrixClient
+
diff --git a/lib/events/event.h b/lib/events/event.h
index b7bbd83e..b3a58806 100644
--- a/lib/events/event.h
+++ b/lib/events/event.h
@@ -60,14 +60,16 @@ namespace QMatrixClient
static const auto ContentKey = QStringLiteral("content");
static const auto EventIdKey = QStringLiteral("event_id");
static const auto UnsignedKey = QStringLiteral("unsigned");
+ static const auto StateKeyKey = QStringLiteral("state_key");
static const auto TypeKeyL = "type"_ls;
static const auto ContentKeyL = "content"_ls;
static const auto EventIdKeyL = "event_id"_ls;
static const auto UnsignedKeyL = "unsigned"_ls;
static const auto RedactedCauseKeyL = "redacted_because"_ls;
static const auto PrevContentKeyL = "prev_content"_ls;
+ static const auto StateKeyKeyL = "state_key"_ls;
- // Minimal correct Matrix event JSON
+ /// Make a minimal correct Matrix event JSON
template <typename StrT>
inline QJsonObject basicEventJson(StrT matrixType,
const QJsonObject& content)
@@ -259,7 +261,7 @@ namespace QMatrixClient
}
template <typename T>
- T content(const QLatin1String& key) const
+ T content(QLatin1String key) const
{
return fromJson<T>(contentJson()[key]);
}
diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h
index ab31a75d..254eb9a9 100644
--- a/lib/events/eventcontent.h
+++ b/lib/events/eventcontent.h
@@ -53,6 +53,9 @@ namespace QMatrixClient
QJsonObject originalJson;
protected:
+ Base(const Base&) = default;
+ Base(Base&&) = default;
+
virtual void fillJson(QJsonObject* o) const = 0;
};
@@ -167,11 +170,14 @@ namespace QMatrixClient
class TypedBase: public Base
{
public:
- explicit TypedBase(const QJsonObject& o = {}) : Base(o) { }
+ explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) { }
virtual QMimeType type() const = 0;
virtual const FileInfo* fileInfo() const { return nullptr; }
virtual FileInfo* fileInfo() { return nullptr; }
virtual const Thumbnail* thumbnailInfo() const { return nullptr; }
+
+ protected:
+ using Base::Base;
};
/**
diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h
index da663392..d0fa60a2 100644
--- a/lib/events/eventloader.h
+++ b/lib/events/eventloader.h
@@ -32,7 +32,8 @@ namespace QMatrixClient {
}
}
- /** Create an event with proper type from a JSON object
+ /*! Create an event with proper type from a JSON object
+ *
* Use this factory template to detect the type from the JSON object
* contents (the detected event type should derive from the template
* parameter type) and create an event object of that type.
@@ -44,7 +45,8 @@ namespace QMatrixClient {
fullJson[TypeKeyL].toString());
}
- /** Create an event from a type string and content JSON
+ /*! Create an event from a type string and content JSON
+ *
* Use this factory template to resolve the C++ type from the Matrix
* type string in \p matrixType and create an event of that type that has
* its content part set to \p content.
@@ -57,6 +59,20 @@ namespace QMatrixClient {
matrixType);
}
+ /*! Create a state event from a type string, content JSON and state key
+ *
+ * Use this factory to resolve the C++ type from the Matrix type string
+ * in \p matrixType and create a state event of that type with content part
+ * set to \p content and state key set to \p stateKey (empty by default).
+ */
+ inline StateEventPtr loadStateEvent(const QString& matrixType,
+ const QJsonObject& content,
+ const QString& stateKey = {})
+ {
+ return _impl::loadEvent<StateEventBase>(
+ basicStateEventJson(matrixType, content, stateKey), matrixType);
+ }
+
template <typename EventT> struct JsonConverter<event_ptr_tt<EventT>>
{
static auto load(const QJsonValue& jv)
diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp
index 3d03509f..f1e563ff 100644
--- a/lib/events/roomevent.cpp
+++ b/lib/events/roomevent.cpp
@@ -78,7 +78,17 @@ QString RoomEvent::transactionId() const
QString RoomEvent::stateKey() const
{
- return fullJson()["state_key"_ls].toString();
+ return fullJson()[StateKeyKeyL].toString();
+}
+
+void RoomEvent::setRoomId(const QString& roomId)
+{
+ editJson().insert(QStringLiteral("room_id"), roomId);
+}
+
+void RoomEvent::setSender(const QString& senderId)
+{
+ editJson().insert(QStringLiteral("sender"), senderId);
}
void RoomEvent::setTransactionId(const QString& txnId)
diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h
index ce96174e..e26a7135 100644
--- a/lib/events/roomevent.h
+++ b/lib/events/roomevent.h
@@ -60,6 +60,9 @@ namespace QMatrixClient
QString transactionId() const;
QString stateKey() const;
+ void setRoomId(const QString& roomId);
+ void setSender(const QString& senderId);
+
/**
* Sets the transaction id for locally created events. This should be
* done before the event is exposed to any code using the respective
diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h
index b8224033..39aa280c 100644
--- a/lib/events/roommemberevent.h
+++ b/lib/events/roommemberevent.h
@@ -56,8 +56,14 @@ namespace QMatrixClient
explicit RoomMemberEvent(const QJsonObject& obj)
: StateEvent(typeId(), obj)
{ }
+ [[deprecated("Use RoomMemberEvent(userId, contentArgs) instead")]]
RoomMemberEvent(MemberEventContent&& c)
- : StateEvent(typeId(), matrixTypeId(), c)
+ : StateEvent(typeId(), matrixTypeId(), QString(), c)
+ { }
+ template <typename... ArgTs>
+ RoomMemberEvent(const QString& userId, ArgTs&&... contentArgs)
+ : StateEvent(typeId(), matrixTypeId(), userId,
+ std::forward<ArgTs>(contentArgs)...)
{ }
/// A special constructor to create unknown RoomMemberEvents
@@ -76,7 +82,7 @@ namespace QMatrixClient
MembershipType membership() const { return content().membership; }
QString userId() const
- { return fullJson()["state_key"_ls].toString(); }
+ { return fullJson()[StateKeyKeyL].toString(); }
bool isDirect() const { return content().isDirect; }
QString displayName() const { return content().displayName; }
QUrl avatarUrl() const { return content().avatarUrl; }
diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h
index 2c23d9ca..ef56c7b2 100644
--- a/lib/events/simplestateevents.h
+++ b/lib/events/simplestateevents.h
@@ -20,8 +20,6 @@
#include "stateevent.h"
-#include "converters.h"
-
namespace QMatrixClient
{
namespace EventContent
@@ -63,7 +61,7 @@ namespace QMatrixClient
explicit _Name() : _Name(value_type()) { } \
template <typename T> \
explicit _Name(T&& value) \
- : StateEvent(typeId(), matrixTypeId(), \
+ : StateEvent(typeId(), matrixTypeId(), QString(), \
QStringLiteral(#_ContentKey), \
std::forward<T>(value)) \
{ } \
@@ -78,15 +76,27 @@ namespace QMatrixClient
DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name)
DEFINE_EVENTTYPE_ALIAS(RoomName, RoomNameEvent)
- DEFINE_SIMPLE_STATE_EVENT(RoomAliasesEvent, "m.room.aliases",
- QStringList, aliases)
- DEFINE_EVENTTYPE_ALIAS(RoomAliases, RoomAliasesEvent)
DEFINE_SIMPLE_STATE_EVENT(RoomCanonicalAliasEvent, "m.room.canonical_alias",
QString, alias)
DEFINE_EVENTTYPE_ALIAS(RoomCanonicalAlias, RoomCanonicalAliasEvent)
DEFINE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic", QString, topic)
DEFINE_EVENTTYPE_ALIAS(RoomTopic, RoomTopicEvent)
- DEFINE_SIMPLE_STATE_EVENT(EncryptionEvent, "m.room.encryption",
- QString, algorithm)
DEFINE_EVENTTYPE_ALIAS(RoomEncryption, EncryptionEvent)
+
+ class RoomAliasesEvent
+ : public StateEvent<EventContent::SimpleContent<QStringList>>
+ {
+ public:
+ DEFINE_EVENT_TYPEID("m.room.aliases", RoomAliasesEvent)
+ explicit RoomAliasesEvent(const QJsonObject& obj)
+ : StateEvent(typeId(), obj, QStringLiteral("aliases"))
+ { }
+ RoomAliasesEvent(const QString& server, const QStringList& aliases)
+ : StateEvent(typeId(), matrixTypeId(), server,
+ QStringLiteral("aliases"), aliases)
+ { }
+ QString server() const { return stateKey(); }
+ QStringList aliases() const { return content().value; }
+ };
+ REGISTER_EVENT_TYPE(RoomAliasesEvent)
} // namespace QMatrixClient
diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp
index a84f302b..6a6e7782 100644
--- a/lib/events/stateevent.cpp
+++ b/lib/events/stateevent.cpp
@@ -27,7 +27,7 @@ using namespace QMatrixClient;
RoomEvent::factory_t::addMethod(
[] (const QJsonObject& json, const QString& matrixType) -> StateEventPtr
{
- if (!json.contains("state_key"_ls))
+ if (!json.contains(StateKeyKeyL))
return nullptr;
if (auto e = StateEventBase::factory_t::make(json, matrixType))
@@ -36,6 +36,12 @@ using namespace QMatrixClient;
return makeEvent<StateEventBase>(unknownEventTypeId(), json);
});
+StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType,
+ const QString &stateKey,
+ const QJsonObject &contentJson)
+ : RoomEvent(type, basicStateEventJson(matrixType, contentJson, stateKey))
+{ }
+
bool StateEventBase::repeatsState() const
{
const auto prevContentJson = unsignedJson().value(PrevContentKeyL);
diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h
index 3f54f7bf..3b56a265 100644
--- a/lib/events/stateevent.h
+++ b/lib/events/stateevent.h
@@ -21,12 +21,28 @@
#include "roomevent.h"
namespace QMatrixClient {
+
+ /// Make a minimal correct Matrix state event JSON
+ template <typename StrT>
+ inline QJsonObject basicStateEventJson(StrT matrixType,
+ const QJsonObject& content, const QString& stateKey = {})
+ {
+ return { { TypeKey, std::forward<StrT>(matrixType) },
+ { StateKeyKey, stateKey },
+ { ContentKey, content } };
+ }
+
class StateEventBase: public RoomEvent
{
public:
using factory_t = EventFactory<StateEventBase>;
- using RoomEvent::RoomEvent;
+ StateEventBase(Type type, const QJsonObject& json)
+ : RoomEvent(type, json)
+ { }
+ StateEventBase(Type type, event_mtype_t matrixType,
+ const QString& stateKey = {},
+ const QJsonObject& contentJson = {});
~StateEventBase() override = default;
bool isStateEvent() const override { return true; }
@@ -83,8 +99,9 @@ namespace QMatrixClient {
}
template <typename... ContentParamTs>
explicit StateEvent(Type type, event_mtype_t matrixType,
+ const QString& stateKey,
ContentParamTs&&... contentParams)
- : StateEventBase(type, matrixType)
+ : StateEventBase(type, matrixType, stateKey)
, _content(std::forward<ContentParamTs>(contentParams)...)
{
editJson().insert(ContentKey, _content.toJson());
diff --git a/lib/joinstate.h b/lib/joinstate.h
index 379183f6..4ae67de8 100644
--- a/lib/joinstate.h
+++ b/lib/joinstate.h
@@ -41,7 +41,7 @@ namespace QMatrixClient
inline const char* toCString(JoinState js)
{
size_t state = size_t(js), index = 0;
- while (state >>= 1) ++index;
+ while (state >>= 1u) ++index;
return JoinStateStrings[index];
}
} // namespace QMatrixClient
diff --git a/lib/logging.cpp b/lib/logging.cpp
index 7476781f..a0c7f7bb 100644
--- a/lib/logging.cpp
+++ b/lib/logging.cpp
@@ -25,9 +25,9 @@
#endif
// Use LOGGING_CATEGORY instead of Q_LOGGING_CATEGORY in the rest of the code
-LOGGING_CATEGORY(MAIN, "libqmatrixclient.main")
-LOGGING_CATEGORY(PROFILER, "libqmatrixclient.profiler")
-LOGGING_CATEGORY(EVENTS, "libqmatrixclient.events")
-LOGGING_CATEGORY(EPHEMERAL, "libqmatrixclient.events.ephemeral")
-LOGGING_CATEGORY(JOBS, "libqmatrixclient.jobs")
-LOGGING_CATEGORY(SYNCJOB, "libqmatrixclient.jobs.sync")
+LOGGING_CATEGORY(MAIN, "quotient.main")
+LOGGING_CATEGORY(PROFILER, "quotient.profiler")
+LOGGING_CATEGORY(EVENTS, "quotient.events")
+LOGGING_CATEGORY(EPHEMERAL, "quotient.events.ephemeral")
+LOGGING_CATEGORY(JOBS, "quotient.jobs")
+LOGGING_CATEGORY(SYNCJOB, "quotient.jobs.sync")
diff --git a/lib/room.cpp b/lib/room.cpp
index 06f3490c..cb368d9e 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -31,6 +31,7 @@
#include "csapi/tags.h"
#include "csapi/room_upgrades.h"
#include "events/simplestateevents.h"
+#include "events/encryptionevent.h"
#include "events/roomcreateevent.h"
#include "events/roomtombstoneevent.h"
#include "events/roomavatarevent.h"
@@ -93,6 +94,8 @@ class Room::Private
/// The state of the room at timeline position before-0
/// \sa timelineBase
std::unordered_map<StateEventKey, StateEventPtr> baseState;
+ /// State event stubs - events without content, just type and state key
+ static decltype(baseState) stubbedState;
/// The state of the room at timeline position after-maxTimelineIndex()
/// \sa Room::syncEdge
QHash<StateEventKey, const StateEventBase*> currentState;
@@ -193,9 +196,22 @@ class Room::Private
template <typename EventT>
const EventT* getCurrentState(const QString& stateKey = {}) const
{
- static const EventT empty;
- const auto* evt =
- currentState.value({EventT::matrixTypeId(), stateKey}, &empty);
+ const StateEventKey evtKey { EventT::matrixTypeId(), stateKey };
+ const auto* evt = currentState.value(evtKey, nullptr);
+ if (!evt) {
+ if (stubbedState.find(evtKey) == stubbedState.end()) {
+ // In the absence of a real event, make a stub as-if an event
+ // with empty content has been received. Event classes should be
+ // prepared for empty/invalid/malicious content anyway.
+ stubbedState.emplace(evtKey,
+ loadStateEvent(EventT::matrixTypeId(),
+ {}, stateKey));
+ qCDebug(MAIN) << "A new stub event created for key {"
+ << evtKey.first << evtKey.second << "}";
+ }
+ evt = stubbedState[evtKey].get();
+ Q_ASSERT(evt);
+ }
Q_ASSERT(evt->type() == EventT::typeId() &&
evt->matrixType() == EventT::matrixTypeId());
return static_cast<const EventT*>(evt);
@@ -272,28 +288,26 @@ class Room::Private
QString doSendEvent(const RoomEvent* pEvent);
void onEventSendingFailure(const QString& txnId, BaseJob* call = nullptr);
- template <typename EvT>
- SetRoomStateWithKeyJob* requestSetState(const QString& stateKey,
- const EvT& event)
+ SetRoomStateWithKeyJob* requestSetState(const StateEventBase& event)
{
- if (q->successorId().isEmpty())
- {
- // TODO: Queue up state events sending (see #133).
- return connection->callApi<SetRoomStateWithKeyJob>(
- id, EvT::matrixTypeId(), stateKey, event.contentJson());
- }
- qCWarning(MAIN) << q << "has been upgraded, state won't be set";
- return nullptr;
+// if (event.roomId().isEmpty())
+// event.setRoomId(id);
+// if (event.senderId().isEmpty())
+// event.setSender(connection->userId());
+ // TODO: Queue up state events sending (see #133).
+ // TODO: Maybe addAsPending() as well, despite having no txnId
+ return connection->callApi<SetRoomStateWithKeyJob>(
+ id, event.matrixType(),
+ event.stateKey(), event.contentJson());
}
- template <typename EvT>
- auto requestSetState(const EvT& event)
+ template <typename EvT, typename... ArgTs>
+ auto requestSetState(ArgTs&&... args)
{
- return connection->callApi<SetRoomStateJob>(
- id, EvT::matrixTypeId(), event.contentJson());
+ return requestSetState(EvT(std::forward<ArgTs>(args)...));
}
- /**
+ /**
* @brief Apply redaction to the timeline
*
* Tries to find an event in the timeline and redact it; deletes the
@@ -317,6 +331,8 @@ class Room::Private
}
};
+decltype(Room::Private::baseState) Room::Private::stubbedState { };
+
Room::Room(Connection* connection, QString id, JoinState initialJoinState)
: QObject(connection), d(new Private(connection, id, initialJoinState))
{
@@ -1400,6 +1416,10 @@ RoomEvent* Room::Private::addAsPending(RoomEventPtr&& event)
{
if (event->transactionId().isEmpty())
event->setTransactionId(connection->generateTxnId());
+ if (event->roomId().isEmpty())
+ event->setRoomId(id);
+ if (event->senderId().isEmpty())
+ event->setSender(connection->userId());
auto* pEvent = rawPtr(event);
emit q->pendingEventAboutToAdd(pEvent);
unsyncedEvents.emplace_back(move(event));
@@ -1409,6 +1429,11 @@ RoomEvent* Room::Private::addAsPending(RoomEventPtr&& event)
QString Room::Private::sendEvent(RoomEventPtr&& event)
{
+ if (q->usesEncryption())
+ {
+ qCCritical(MAIN) << "Room" << q->objectName()
+ << "enforces encryption; sending encrypted messages is not supported yet";
+ }
if (q->successorId().isEmpty())
return doSendEvent(addAsPending(std::move(event)));
@@ -1614,38 +1639,38 @@ QString Room::postFile(const QString& plainText, const QUrl& localPath,
QString Room::postEvent(RoomEvent* event)
{
- if (usesEncryption())
- {
- qCCritical(MAIN) << "Room" << displayName()
- << "enforces encryption; sending encrypted messages is not supported yet";
- }
return d->sendEvent(RoomEventPtr(event));
}
QString Room::postJson(const QString& matrixType,
const QJsonObject& eventContent)
{
- return d->sendEvent(loadEvent<RoomEvent>(basicEventJson(matrixType, eventContent)));
+ return d->sendEvent(loadEvent<RoomEvent>(matrixType, eventContent));
+}
+
+SetRoomStateWithKeyJob* Room::setState(const StateEventBase& evt) const {
+ return d->requestSetState(evt);
}
void Room::setName(const QString& newName)
{
- d->requestSetState(RoomNameEvent(newName));
+ d->requestSetState<RoomNameEvent>(newName);
}
void Room::setCanonicalAlias(const QString& newAlias)
{
- d->requestSetState(RoomCanonicalAliasEvent(newAlias));
+ d->requestSetState<RoomCanonicalAliasEvent>(newAlias);
}
void Room::setLocalAliases(const QStringList& aliases)
{
- d->requestSetState(RoomAliasesEvent(aliases));
+ d->requestSetState<RoomAliasesEvent>(
+ connection()->homeserver().authority(), aliases);
}
void Room::setTopic(const QString& newTopic)
{
- d->requestSetState(RoomTopicEvent(newTopic));
+ d->requestSetState<RoomTopicEvent>(newTopic);
}
bool isEchoEvent(const RoomEventPtr& le, const PendingEventItem& re)
@@ -1694,33 +1719,33 @@ void Room::inviteCall(const QString& callId, const int lifetime,
const QString& sdp)
{
Q_ASSERT(supportsCalls());
- postEvent(new CallInviteEvent(callId, lifetime, sdp));
+ d->sendEvent<CallInviteEvent>(callId, lifetime, sdp);
}
void Room::sendCallCandidates(const QString& callId,
const QJsonArray& candidates)
{
Q_ASSERT(supportsCalls());
- postEvent(new CallCandidatesEvent(callId, candidates));
+ d->sendEvent<CallCandidatesEvent>(callId, candidates);
}
void Room::answerCall(const QString& callId, const int lifetime,
const QString& sdp)
{
Q_ASSERT(supportsCalls());
- postEvent(new CallAnswerEvent(callId, lifetime, sdp));
+ d->sendEvent<CallAnswerEvent>(callId, lifetime, sdp);
}
void Room::answerCall(const QString& callId, const QString& sdp)
{
Q_ASSERT(supportsCalls());
- postEvent(new CallAnswerEvent(callId, sdp));
+ d->sendEvent<CallAnswerEvent>(callId, sdp);
}
void Room::hangupCall(const QString& callId)
{
Q_ASSERT(supportsCalls());
- postEvent(new CallHangupEvent(callId));
+ d->sendEvent<CallHangupEvent>(callId);
}
void Room::getPreviousContent(int limit)
@@ -1755,9 +1780,10 @@ LeaveRoomJob* Room::leaveRoom()
return connection()->leaveRoom(this);
}
-SetRoomStateWithKeyJob*Room::setMemberState(const QString& memberId, const RoomMemberEvent& event) const
+SetRoomStateWithKeyJob* Room::setMemberState(
+ const QString& memberId, const RoomMemberEvent& event) const
{
- return d->requestSetState(memberId, event);
+ return d->requestSetState<RoomMemberEvent>(memberId, event.content());
}
void Room::kickMember(const QString& memberId, const QString& reason)
@@ -1919,7 +1945,7 @@ RoomEventPtr makeRedacted(const RoomEvent& target,
auto originalJson = target.originalJsonObject();
static const QStringList keepKeys {
EventIdKey, TypeKey, QStringLiteral("room_id"),
- QStringLiteral("sender"), QStringLiteral("state_key"),
+ QStringLiteral("sender"), StateKeyKey,
QStringLiteral("prev_content"), ContentKey,
QStringLiteral("hashes"), QStringLiteral("signatures"),
QStringLiteral("depth"), QStringLiteral("prev_events"),
diff --git a/lib/room.h b/lib/room.h
index 7c85e4ed..c79ca1e0 100644
--- a/lib/room.h
+++ b/lib/room.h
@@ -18,6 +18,7 @@
#pragma once
+#include "connection.h"
#include "csapi/message_pagination.h"
#include "events/roommessageevent.h"
#include "events/accountdataevents.h"
@@ -36,7 +37,6 @@ namespace QMatrixClient
class Avatar;
class SyncRoomData;
class RoomMemberEvent;
- class Connection;
class User;
class MemberSorter;
class LeaveRoomJob;
@@ -406,17 +406,14 @@ namespace QMatrixClient
MemberSorter memberSorter() const;
- Q_INVOKABLE void inviteCall(const QString& callId,
- const int lifetime, const QString& sdp);
- Q_INVOKABLE void sendCallCandidates(const QString& callId,
- const QJsonArray& candidates);
- Q_INVOKABLE void answerCall(const QString& callId, const int lifetime,
- const QString& sdp);
- Q_INVOKABLE void answerCall(const QString& callId,
- const QString& sdp);
- Q_INVOKABLE void hangupCall(const QString& callId);
Q_INVOKABLE bool supportsCalls() const;
+ template <typename EvT, typename... ArgTs>
+ auto setState(ArgTs&&... args) const
+ {
+ return setState(EvT(std::forward<ArgTs>(args)...));
+ }
+
public slots:
/** Check whether the room should be upgraded */
void checkVersion();
@@ -440,6 +437,9 @@ namespace QMatrixClient
const QJsonObject& eventContent);
QString retryMessage(const QString& txnId);
void discardMessage(const QString& txnId);
+
+ /// Send a request to update the room state with the given event
+ SetRoomStateWithKeyJob* setState(const StateEventBase& evt) const;
void setName(const QString& newName);
void setCanonicalAlias(const QString& newAlias);
/// Set room aliases on the user's current server
@@ -453,6 +453,7 @@ namespace QMatrixClient
void inviteToRoom(const QString& memberId);
LeaveRoomJob* leaveRoom();
+ /// \deprecated - use setState() instead")
SetRoomStateWithKeyJob* setMemberState(
const QString& memberId, const RoomMemberEvent& event) const;
void kickMember(const QString& memberId, const QString& reason = {});
@@ -477,6 +478,15 @@ namespace QMatrixClient
/// Switch the room's version (aka upgrade)
void switchVersion(QString newVersion);
+ void inviteCall(const QString& callId,
+ const int lifetime, const QString& sdp);
+ void sendCallCandidates(const QString& callId,
+ const QJsonArray& candidates);
+ void answerCall(const QString& callId, const int lifetime,
+ const QString& sdp);
+ void answerCall(const QString& callId, const QString& sdp);
+ void hangupCall(const QString& callId);
+
signals:
/// Initial set of state events has been loaded
/**
diff --git a/lib/settings.cpp b/lib/settings.cpp
index 124d7042..5f10299c 100644
--- a/lib/settings.cpp
+++ b/lib/settings.cpp
@@ -90,6 +90,7 @@ QMC_DEFINE_SETTING(AccountSettings, bool, keepLoggedIn, "keep_logged_in", false,
static const auto HomeserverKey = QStringLiteral("homeserver");
static const auto AccessTokenKey = QStringLiteral("access_token");
+static const auto EncryptionAccountPickleKey = QStringLiteral("encryption_account_pickle");
QUrl AccountSettings::homeserver() const
{
@@ -114,7 +115,7 @@ QString AccountSettings::accessToken() const
void AccountSettings::setAccessToken(const QString& accessToken)
{
qCWarning(MAIN) << "Saving access_token to QSettings is insecure."
- " Developers, please save access_token separately.";
+ " Developers, do it manually or contribute to share QtKeychain logic to libQuotient.";
setValue(AccessTokenKey, accessToken);
}
@@ -124,3 +125,22 @@ void AccountSettings::clearAccessToken()
legacySettings.remove(QStringLiteral("device_id")); // Force the server to re-issue it
remove(AccessTokenKey);
}
+
+QByteArray AccountSettings::encryptionAccountPickle()
+{
+ QString passphrase = ""; // FIXME: add QtKeychain
+ return value("encryption_account_pickle", "").toByteArray();
+}
+
+void AccountSettings::setEncryptionAccountPickle(const QByteArray& encryptionAccountPickle)
+{
+ qCWarning(MAIN) << "Saving encryption_account_pickle to QSettings is insecure."
+ " Developers, do it manually or contribute to share QtKeychain logic to libQuotient.";
+ QString passphrase = ""; // FIXME: add QtKeychain
+ setValue("encryption_account_pickle", encryptionAccountPickle);
+}
+
+void AccountSettings::clearEncryptionAccountPickle()
+{
+ remove(EncryptionAccountPickleKey); // TODO: Force to re-issue it?
+}
diff --git a/lib/settings.h b/lib/settings.h
index 759bda35..61e5232a 100644
--- a/lib/settings.h
+++ b/lib/settings.h
@@ -131,6 +131,7 @@ void classname::setter(type newValue) \
QMC_DECLARE_SETTING(bool, keepLoggedIn, setKeepLoggedIn)
/** \deprecated \sa setAccessToken */
Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken)
+ Q_PROPERTY(QByteArray encryptionAccountPickle READ encryptionAccountPickle WRITE setEncryptionAccountPickle)
public:
template <typename... ArgTs>
explicit AccountSettings(const QString& accountId, ArgTs... qsettingsArgs)
@@ -148,5 +149,9 @@ void classname::setter(type newValue) \
* see QMatrixClient/Quaternion#181 */
void setAccessToken(const QString& accessToken);
Q_INVOKABLE void clearAccessToken();
+
+ QByteArray encryptionAccountPickle();
+ void setEncryptionAccountPickle(const QByteArray& encryptionAccountPickle);
+ Q_INVOKABLE void clearEncryptionAccountPickle();
};
} // namespace QMatrixClient
diff --git a/lib/user.cpp b/lib/user.cpp
index 7b695618..7ca97b1a 100644
--- a/lib/user.cpp
+++ b/lib/user.cpp
@@ -292,7 +292,7 @@ void User::rename(const QString& newName, const Room* r)
const auto actualNewName = sanitized(newName);
MemberEventContent evtC;
evtC.displayName = actualNewName;
- connect(r->setMemberState(id(), RoomMemberEvent(move(evtC))),
+ connect(r->setState<RoomMemberEvent>(id(), move(evtC)),
&BaseJob::success, this, [=] { updateName(actualNewName, r); });
}
diff --git a/lib/util.cpp b/lib/util.cpp
index 88cba959..b9639843 100644
--- a/lib/util.cpp
+++ b/lib/util.cpp
@@ -112,6 +112,20 @@ qreal QMatrixClient::stringToHueF(const QString &string)
return hueF;
}
+static const auto ServerPartRegEx = QStringLiteral(
+ "(\\[[^]]+\\]|[^:@]+)" // Either IPv6 address or hostname/IPv4 address
+ "(?::(\\d{1,5}))?" // Optional port
+);
+
+QString QMatrixClient::serverPart(const QString& mxId)
+{
+ static QString re = "^[@!#$+].+?:(" // Localpart and colon
+ % ServerPartRegEx % ")$";
+ static QRegularExpression parser(re,
+ QRegularExpression::UseUnicodePropertiesOption); // Because Asian digits
+ return parser.match(mxId).captured(1);
+}
+
// Tests for function_traits<>
#ifdef Q_CC_CLANG
diff --git a/lib/util.h b/lib/util.h
index eda817a1..1106e774 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -300,27 +300,33 @@ namespace QMatrixClient
void linkifyUrls(QString& htmlEscapedText);
/** Sanitize the text before showing in HTML
+ *
* This does toHtmlEscaped() and removes Unicode BiDi marks.
*/
QString sanitized(const QString& plainText);
/** Pretty-print plain text into HTML
+ *
* This includes HTML escaping of <,>,",& and calling linkifyUrls()
*/
QString prettyPrint(const QString& plainText);
/** Return a path to cache directory after making sure that it exists
+ *
* The returned path has a trailing slash, clients don't need to append it.
* \param dir path to cache directory relative to the standard cache path
*/
QString cacheLocation(const QString& dirName);
/** Hue color component of based of the hash of the string.
+ *
* The implementation is based on XEP-0392:
* https://xmpp.org/extensions/xep-0392.html
* Naming and range are the same as QColor's hueF method:
* https://doc.qt.io/qt-5/qcolor.html#integer-vs-floating-point-precision
*/
qreal stringToHueF(const QString& string);
-} // namespace QMatrixClient
+ /** Extract the serverpart from MXID */
+ QString serverPart(const QString& mxId);
+} // namespace QMatrixClient