From 26c51455901a6c0112531d86d61d65d31a70dfaa Mon Sep 17 00:00:00 2001 From: Ville Ranki Date: Thu, 4 Apr 2019 11:45:41 +0300 Subject: Ignore some errors on leaving rooms, add new error enum. Fixes #307 --- lib/connection.cpp | 56 ++++++++++++++++++++++++++++++++++++---------------- lib/jobs/basejob.cpp | 3 +++ lib/jobs/basejob.h | 1 + 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index a7eae30f..5bf89815 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -110,6 +110,7 @@ class Connection::Private void connectWithToken(const QString& user, const QString& accessToken, const QString& deviceId); + void removeRoom(const QString& roomId); template EventT* unpackAccountData() const @@ -790,29 +791,36 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) if (room && room->joinState() != JoinState::Leave) { auto leaveJob = room->leaveRoom(); - connect(leaveJob, &BaseJob::success, this, [this, forgetJob, room] { - forgetJob->start(connectionData()); - // If the matching /sync response hasn't arrived yet, mark the room - // for explicit deletion - if (room->joinState() != JoinState::Leave) - d->roomIdsToForget.push_back(room->id()); + connect(leaveJob, &BaseJob::result, this, [this, leaveJob, forgetJob, room] { + // After leave, continue if there is no error or the room id is not found (IncorrectRequestError) + if(!leaveJob->error() || leaveJob->error() == BaseJob::StatusCode::UnknownObjectError) { + forgetJob->start(connectionData()); + // If the matching /sync response hasn't arrived yet, mark the room + // for explicit deletion + if (room->joinState() != JoinState::Leave) + d->roomIdsToForget.push_back(room->id()); + } else { + qCWarning(MAIN) << "Error leaving room " + << room->name() << ":" + << leaveJob->errorString(); + forgetJob->abandon(); + } }); connect(leaveJob, &BaseJob::failure, forgetJob, &BaseJob::abandon); } else forgetJob->start(connectionData()); - connect(forgetJob, &BaseJob::success, this, [this, id] + connect(forgetJob, &BaseJob::result, this, [this, id, forgetJob] { - // Delete whatever instances of the room are still in the map. - for (auto f: {false, true}) - if (auto r = d->roomMap.take({ id, f })) - { - qCDebug(MAIN) << "Room" << r->objectName() - << "in state" << toCString(r->joinState()) - << "will be deleted"; - emit r->beforeDestruction(r); - r->deleteLater(); - } + // Leave room in case of success, or room not known by server + if(!forgetJob->error() || forgetJob->error() == BaseJob::StatusCode::IncorrectRequestError || forgetJob->error() == BaseJob::StatusCode::UnknownObjectError) { + // Delete the room from roomMap + d->removeRoom(id); + } else { + qCWarning(MAIN) << "Error forgetting room " + << id << ":" + << forgetJob->errorString(); + } }); return forgetJob; } @@ -1056,6 +1064,20 @@ Connection::DirectChatsMap Connection::directChats() const return d->directChats; } +// Removes room with given id from roomMap +void Connection::Private::removeRoom(const QString& roomId) +{ + for (auto f: {false, true}) + if (auto r = roomMap.take({ roomId, f })) + { + qCDebug(MAIN) << "Room" << r->objectName() + << "in state" << toCString(r->joinState()) + << "will be deleted"; + emit r->beforeDestruction(r); + r->deleteLater(); + } +} + void Connection::addToDirectChats(const Room* room, User* user) { Q_ASSERT(room != nullptr && user != nullptr); diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 0d9b9f10..97b5a904 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -325,6 +325,9 @@ void BaseJob::gotReply() d->status.code = UserConsentRequiredError; d->errorUrl = json.value("consent_uri"_ls).toString(); } + else if (errCode == "M_UNKNOWN") { + d->status.code = UnknownObjectError; + } else if (errCode == "M_UNSUPPORTED_ROOM_VERSION" || errCode == "M_INCOMPATIBLE_ROOM_VERSION") { diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 4c1c7706..c1747cca 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -67,6 +67,7 @@ namespace QMatrixClient , UnsupportedRoomVersionError , NetworkAuthRequiredError , UserConsentRequiredError + , UnknownObjectError // Unknown room or other item (M_UNKNOWN) , UserDefinedError = 200 }; -- cgit v1.2.3 From 7591ec34cee15a58611408a996bdb1b92b6ffb98 Mon Sep 17 00:00:00 2001 From: Ville Ranki Date: Tue, 11 Jun 2019 11:58:57 +0300 Subject: Remove unnecessary error checks in lib/connection.cpp Co-Authored-By: Kitsune Ral --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 5bf89815..27211a77 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -813,7 +813,7 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) connect(forgetJob, &BaseJob::result, this, [this, id, forgetJob] { // Leave room in case of success, or room not known by server - if(!forgetJob->error() || forgetJob->error() == BaseJob::StatusCode::IncorrectRequestError || forgetJob->error() == BaseJob::StatusCode::UnknownObjectError) { + if(!forgetJob->error() || forgetJob->error() == BaseJob::UnknownObjectError) { // Delete the room from roomMap d->removeRoom(id); } else { -- cgit v1.2.3 From a19e12544d174588bb99d1d9d5b2576f0ea1e037 Mon Sep 17 00:00:00 2001 From: Ville Ranki Date: Tue, 11 Jun 2019 11:59:40 +0300 Subject: Comment change as requested Co-Authored-By: Kitsune Ral --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 27211a77..af2aa2ab 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -792,7 +792,7 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) { auto leaveJob = room->leaveRoom(); connect(leaveJob, &BaseJob::result, this, [this, leaveJob, forgetJob, room] { - // After leave, continue if there is no error or the room id is not found (IncorrectRequestError) + // After leave, continue if there is no error or the room id is not found if(!leaveJob->error() || leaveJob->error() == BaseJob::StatusCode::UnknownObjectError) { forgetJob->start(connectionData()); // If the matching /sync response hasn't arrived yet, mark the room -- cgit v1.2.3 From 90623dd4a191d4cce2ddd514f313f689d0a135b0 Mon Sep 17 00:00:00 2001 From: Felix Rohrbach Date: Sun, 2 Oct 2016 08:01:22 +0900 Subject: Include olm via cmake (should work on Linux and MinGW) --- .travis.yml | 7 +++++++ CMakeLists.txt | 13 ++++++++++++- cmake/FindOlm.cmake | 30 ++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 cmake/FindOlm.cmake diff --git a/.travis.yml b/.travis.yml index 3aaa4039..72be46cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,13 @@ before_install: - if [ "$TRAVIS_OS_NAME" = "linux" ]; then USE_NINJA="-GNinja"; VALGRIND="valgrind $VALGRIND_OPTIONS"; . /opt/qt57/bin/qt57-env.sh; fi install: +# olm +- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; else sudo apt-get update -qq; fi +- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install qt5; export PATH="$PATH:/usr/local/opt/qt/bin"; else sudo apt-get install -y qt5-default; fi +- git clone https://matrix.org/git/olm.git && cd olm && make && sudo make install && cd .. +- mkdir build && cd build +- cmake .. +# matrix-doc and gtad - git clone https://github.com/QMatrixClient/matrix-doc.git - git clone --recursive https://github.com/KitsuneRal/gtad.git - pushd gtad diff --git a/CMakeLists.txt b/CMakeLists.txt index ca597469..6718000c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,12 @@ if (NOT WIN32) include(GNUInstallDirs) endif(NOT WIN32) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +include(cmake/FindOlm.cmake) + +# Find includes in corresponding build directories +set(CMAKE_INCLUDE_CURRENT_DIR ON) + # Instruct CMake to run moc automatically when needed. set(CMAKE_AUTOMOC ON) @@ -47,6 +53,8 @@ endforeach () # upstream Qt 5.4 is required. find_package(Qt5 5.4.1 REQUIRED Network Gui Multimedia) get_filename_component(Qt5_Prefix "${Qt5_DIR}/../../../.." ABSOLUTE) +find_package(Olm REQUIRED) +get_filename_component(Olm_Prefix "${Olm_INCLUDE_DIRS}/.." ABSOLUTE) if (GTAD_PATH) get_filename_component(ABS_GTAD_PATH "${GTAD_PATH}" ABSOLUTE) @@ -64,11 +72,13 @@ if (CMAKE_BUILD_TYPE) message( STATUS "Build type: ${CMAKE_BUILD_TYPE}") endif(CMAKE_BUILD_TYPE) message( STATUS "Using compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}" ) +message( STATUS "Install Prefix: ${CMAKE_INSTALL_PREFIX}" ) message( STATUS "Using Qt ${Qt5_VERSION} at ${Qt5_Prefix}" ) if (MATRIX_DOC_PATH AND GTAD_PATH) message( STATUS "Generating API stubs enabled" ) message( STATUS " Using GTAD at ${ABS_GTAD_PATH}" ) message( STATUS " Using API files at ${ABS_API_DEF_PATH}" ) + message( STATUS "Using libolm at ${Olm_Prefix}" ) endif () message( STATUS "=============================================================================" ) message( STATUS ) @@ -157,8 +167,9 @@ set_property(TARGET QMatrixClient APPEND PROPERTY target_include_directories(QMatrixClient PUBLIC $ $ + "${Olm_INCLUDE_DIRS}" ) -target_link_libraries(QMatrixClient Qt5::Core Qt5::Network Qt5::Gui Qt5::Multimedia) +target_link_libraries(QMatrixClient Qt5::Core Qt5::Network Qt5::Gui Qt5::Multimedia ${Olm_LIBRARIES}) add_executable(qmc-example ${example_SRCS}) target_link_libraries(qmc-example Qt5::Core QMatrixClient) diff --git a/cmake/FindOlm.cmake b/cmake/FindOlm.cmake new file mode 100644 index 00000000..3fea7b46 --- /dev/null +++ b/cmake/FindOlm.cmake @@ -0,0 +1,30 @@ +# - Try to find LibOlm + +# Uses the following variables to help find libolm: +# Olm_INCLUDE_DIR - include files +# Olm_LIBRARY_DIR - libraries +# Once done this will define +# Olm_FOUND - System has olm +# Olm_INCLUDE_DIRS - The olm include directories +# Olm_LIBRARIES - The libraries needed to use olm + +find_path(Olm_INCLUDE_DIRS NAMES + olm/olm.h + olm/inbound_group_session.h + olm/outbound_group_session.h + PATHS "${Olm_INCLUDE_DIR}" + DOC "Path to a directory with libolm header files" +) + +find_library(Olm_LIBRARIES NAMES olm + PATHS "${Olm_LIBRARY_DIR}" + DOC "Path to a directory with libolm libraries" +) + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set OLM_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(olm DEFAULT_MSG + Olm_LIBRARIES Olm_INCLUDE_DIRS) + +mark_as_advanced(Olm_INCLUDE_DIRS Olm_LIBRARIES) -- cgit v1.2.3 From 1cf67730a0880a520ae04bdf4ef61592daa9fe06 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 26 Sep 2017 20:57:28 +0900 Subject: Introduce EncryptionEvent class This allows to detect if a room has been encrypted (no room state, just an event as of yet). Closes #84. --- CMakeLists.txt | 1 + lib/events/encryptionevent.cpp | 5 +++++ lib/events/encryptionevent.h | 43 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 lib/events/encryptionevent.cpp create mode 100644 lib/events/encryptionevent.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6718000c..92d8a969 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,6 +113,7 @@ set(libqmatrixclient_SRCS lib/events/callhangupevent.cpp lib/events/callinviteevent.cpp lib/events/directchatevent.cpp + lib/events/encryptionevent.cpp lib/jobs/requestdata.cpp lib/jobs/basejob.cpp lib/jobs/syncjob.cpp diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp new file mode 100644 index 00000000..7b620bce --- /dev/null +++ b/lib/events/encryptionevent.cpp @@ -0,0 +1,5 @@ +// +// Created by rusakov on 26/09/2017. +// + +#include "encryptionevent.h" diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h new file mode 100644 index 00000000..b44e0eeb --- /dev/null +++ b/lib/events/encryptionevent.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Copyright (C) 2017 Kitsune Ral + * + * 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 "roomevent.h" + +namespace QMatrixClient +{ + class EncryptionEvent : public RoomEvent + { + public: + DEFINE_EVENT_TYPEID("m.room.encryption", EncryptionEvent) + + explicit EncryptionEvent(const QJsonObject& obj) + : RoomEvent(typeId(), obj) + , _algorithm(contentJson()["algorithm"].toString()) + { } + + QString algorithm() const { return _algorithm; } + + private: + QString _algorithm; + }; + REGISTER_EVENT_TYPE(EncryptionEvent) + DEFINE_EVENTTYPE_ALIAS(Encryption, EncryptionEvent) +} // namespace QMatrixClient + -- cgit v1.2.3 From 12478cf7330727083103d22f76de92c4aa476f5b Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 1 Jul 2019 16:10:35 +0900 Subject: Handle M_UNKNOWN as The Spec says; factor out BaseJob::parseError() --- lib/connection.cpp | 46 ++++++++++++------------- lib/jobs/basejob.cpp | 96 +++++++++++++++++++++++++++------------------------- lib/jobs/basejob.h | 14 ++++++-- 3 files changed, 83 insertions(+), 73 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index af2aa2ab..4c068b8f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -791,21 +791,23 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) if (room && room->joinState() != JoinState::Leave) { auto leaveJob = room->leaveRoom(); - connect(leaveJob, &BaseJob::result, this, [this, leaveJob, forgetJob, room] { - // After leave, continue if there is no error or the room id is not found - if(!leaveJob->error() || leaveJob->error() == BaseJob::StatusCode::UnknownObjectError) { - forgetJob->start(connectionData()); - // If the matching /sync response hasn't arrived yet, mark the room - // for explicit deletion - if (room->joinState() != JoinState::Leave) - d->roomIdsToForget.push_back(room->id()); - } else { - qCWarning(MAIN) << "Error leaving room " - << room->name() << ":" - << leaveJob->errorString(); - forgetJob->abandon(); - } - }); + connect(leaveJob, &BaseJob::result, this, + [this, leaveJob, forgetJob, room] { + if (leaveJob->error() == BaseJob::Success + || leaveJob->error() == BaseJob::NotFoundError) + { + forgetJob->start(connectionData()); + // If the matching /sync response hasn't arrived yet, + // mark the room for explicit deletion + if (room->joinState() != JoinState::Leave) + d->roomIdsToForget.push_back(room->id()); + } else { + qCWarning(MAIN).nospace() + << "Error leaving room " << room->objectName() + << ": " << leaveJob->errorString(); + forgetJob->abandon(); + } + }); connect(leaveJob, &BaseJob::failure, forgetJob, &BaseJob::abandon); } else @@ -813,14 +815,12 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) connect(forgetJob, &BaseJob::result, this, [this, id, forgetJob] { // Leave room in case of success, or room not known by server - if(!forgetJob->error() || forgetJob->error() == BaseJob::UnknownObjectError) { - // Delete the room from roomMap - d->removeRoom(id); - } else { - qCWarning(MAIN) << "Error forgetting room " - << id << ":" - << forgetJob->errorString(); - } + if(forgetJob->error() == BaseJob::Success + || forgetJob->error() == BaseJob::NotFoundError) + d->removeRoom(id); // Delete the room from roomMap + else + qCWarning(MAIN).nospace() << "Error forgetting room " << id << ": " + << forgetJob->errorString(); }); return forgetJob; } diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 97b5a904..dbf197ec 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -289,59 +289,23 @@ void BaseJob::gotReply() if (status().good()) setStatus(parseReply(d->reply.data())); else { - // FIXME: Factor out to smth like BaseJob::handleError() d->rawResponse = d->reply->readAll(); const auto jsonBody = - d->reply->rawHeader("Content-Type") == "application/json"; + d->reply->rawHeader("Content-Type") == "application/json"; qCDebug(d->logCat).noquote() - << "Error body (truncated if long):" << d->rawResponse.left(500); + << "Error body (truncated if long):" << d->rawResponse.left(500); if (jsonBody) - { - auto json = QJsonDocument::fromJson(d->rawResponse).object(); - const auto errCode = json.value("errcode"_ls).toString(); - if (error() == TooManyRequestsError || - errCode == "M_LIMIT_EXCEEDED") - { - QString msg = tr("Too many requests"); - auto retryInterval = json.value("retry_after_ms"_ls).toInt(-1); - if (retryInterval != -1) - msg += tr(", next retry advised after %1 ms") - .arg(retryInterval); - else // We still have to figure some reasonable interval - retryInterval = getNextRetryInterval(); - - setStatus(TooManyRequestsError, msg); - - // Shortcut to retry instead of executing finishJob() - stop(); - qCWarning(d->logCat) - << this << "will retry in" << retryInterval << "ms"; - d->retryTimer.start(retryInterval); - emit retryScheduled(d->retriesTaken, retryInterval); - return; - } - if (errCode == "M_CONSENT_NOT_GIVEN") - { - d->status.code = UserConsentRequiredError; - d->errorUrl = json.value("consent_uri"_ls).toString(); - } - else if (errCode == "M_UNKNOWN") { - d->status.code = UnknownObjectError; - } - else if (errCode == "M_UNSUPPORTED_ROOM_VERSION" || - errCode == "M_INCOMPATIBLE_ROOM_VERSION") - { - d->status.code = UnsupportedRoomVersionError; - if (json.contains("room_version")) - d->status.message = - tr("Requested room version: %1") - .arg(json.value("room_version").toString()); - } else if (!json.isEmpty()) // Not localisable on the client side - setStatus(d->status.code, json.value("error"_ls).toString()); - } + setStatus( + parseError(d->reply.data(), + QJsonDocument::fromJson(d->rawResponse).object())); } - finishJob(); + if (error() != TooManyRequestsError) + finishJob(); + else { + stop(); + emit retryScheduled(d->retriesTaken, d->retryTimer.interval()); + } } bool checkContentType(const QByteArray& type, const QByteArrayList& patterns) @@ -445,8 +409,46 @@ BaseJob::Status BaseJob::parseJson(const QJsonDocument&) return Success; } +BaseJob::Status BaseJob::parseError(QNetworkReply* reply, + const QJsonObject& errorJson) +{ + const auto errCode = errorJson.value("errcode"_ls).toString(); + if (error() == TooManyRequestsError || errCode == "M_LIMIT_EXCEEDED") { + QString msg = tr("Too many requests"); + auto retryInterval = errorJson.value("retry_after_ms"_ls).toInt(-1); + if (retryInterval != -1) + msg += tr(", next retry advised after %1 ms").arg(retryInterval); + else // We still have to figure some reasonable interval + retryInterval = getNextRetryInterval(); + + qCWarning(d->logCat) << this << "will retry in" << retryInterval << "ms"; + d->retryTimer.start(retryInterval); + + return { TooManyRequestsError, msg }; + } + if (errCode == "M_CONSENT_NOT_GIVEN") { + d->errorUrl = errorJson.value("consent_uri"_ls).toString(); + return { UserConsentRequiredError }; + } + if (errCode == "M_UNSUPPORTED_ROOM_VERSION" + || errCode == "M_INCOMPATIBLE_ROOM_VERSION") + return { UnsupportedRoomVersionError, + errorJson.contains("room_version"_ls) + ? tr("Requested room version: %1") + .arg(errorJson.value("room_version"_ls).toString()) + : errorJson.value("error"_ls).toString() }; + + // Not localisable on the client side + if (errorJson.contains("error"_ls)) + d->status.message = errorJson.value("error"_ls).toString(); + + return d->status; +} + void BaseJob::stop() { + // This method is used to semi-finalise the job before retrying; so + // stop the timeout timer but keep the retry timer running. d->timer.stop(); if (d->reply) { diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index c1747cca..30ecceb3 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -67,7 +67,6 @@ namespace QMatrixClient , UnsupportedRoomVersionError , NetworkAuthRequiredError , UserConsentRequiredError - , UnknownObjectError // Unknown room or other item (M_UNKNOWN) , UserDefinedError = 200 }; @@ -303,7 +302,7 @@ namespace QMatrixClient * Processes the reply. By default, parses the reply into * a QJsonDocument and calls parseJson() if it's a valid JSON. * - * @param reply raw contents of a HTTP reply from the server (without headers) + * @param reply raw contents of a HTTP reply from the server * * @see gotReply, parseJson */ @@ -311,7 +310,7 @@ namespace QMatrixClient /** * Processes the JSON document received from the Matrix server. - * By default returns succesful status without analysing the JSON. + * By default returns successful status without analysing the JSON. * * @param json valid JSON document received from the server * @@ -319,6 +318,15 @@ namespace QMatrixClient */ virtual Status parseJson(const QJsonDocument&); + /** + * Processes the reply in case of unsuccessful HTTP code. + * The body is already loaded from the reply object to errorJson. + * @param reply the HTTP reply from the server + * @param errorJson the JSON payload describing the error + */ + virtual Status parseError(QNetworkReply* reply, + const QJsonObject& errorJson); + void setStatus(Status s); void setStatus(int code, QString message); -- cgit v1.2.3 From ddc5a60184972e1449191ce77561b875a145a665 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 2 Jul 2019 08:53:25 +0900 Subject: linkifyUrls: support matrix: scheme and relative URLs --- lib/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.cpp b/lib/util.cpp index 01d4e77b..88cba959 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -45,7 +45,7 @@ void QMatrixClient::linkifyUrls(QString& htmlEscapedText) // <, >, ' or ", and ends before whitespaces, <, >, ', ", ], !, ), :, // comma or dot static const QRegularExpression FullUrlRegExp(QStringLiteral( - R"(\b((www\.(?!\.)(?!(\w|\.|-)+@)|(https?|ftp|magnet)://)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))" + R"(\b((www\.(?!\.)(?!(\w|\.|-)+@)|(https?|ftp|magnet|matrix):(//)?)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))" ), RegExpOptions); // email address: // [word chars, dots or dashes]@[word chars, dots or dashes].[word chars] -- cgit v1.2.3 From 6a6857b9d4dbf22402f2871494bdd06cdccdf366 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 3 Jul 2019 23:28:09 +0900 Subject: Room/Connection: make room aliases work properly Closes #301. --- lib/connection.cpp | 49 ++++++++++++++++++++++++++++++++----------------- lib/connection.h | 10 ++++++---- lib/room.cpp | 39 +++++++++++++++++++++++++++++++++++---- lib/room.h | 14 ++++++++++---- 4 files changed, 83 insertions(+), 29 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 4c068b8f..783e12c0 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -82,8 +82,9 @@ class Connection::Private // separately; specifically, we should keep objects for Invite and // Leave state of the same room if the two happen to co-exist. QHash, Room*> roomMap; - // Mapping from aliases to room ids, as per the last sync - QHash roomAliasMap; + /// Mapping from serverparts to alias/room id mappings, + /// as of the last sync + QHash> roomAliasMap; QVector roomIdsToForget; QVector firstTimeRooms; QVector pendingStateRoomIds; @@ -158,20 +159,31 @@ Connection::~Connection() stopSync(); } -void Connection::resolveServer(const QString& mxidOrDomain) +static const auto ServerPartRegEx = QStringLiteral( + "(\\[[^]]+\\]|[^:@]+)" // Either IPv6 address or hostname/IPv4 address + "(?::(\\d{1,5}))?" // Optional port +); + +QString serverPart(const QString& mxId) { - // At this point we may have something as complex as - // @username:[IPv6:address]:port, or as simple as a plain domain name. + static auto re = "^[@!#$+].+?:(" // Localpart and colon + % ServerPartRegEx % ")$"; + static QRegularExpression parser(re, + QRegularExpression::UseUnicodePropertiesOption); // Because Asian digits + return parser.match(mxId).captured(1); +} - // Try to parse as an FQID; if there's no @ part, assume it's a domain name. - QRegularExpression parser( +void Connection::resolveServer(const QString& mxidOrDomain) +{ + // 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) - "(\\[[^]]+\\]|[^:@]+)" // Either IPv6 address or hostname/IPv4 address - "(:\\d{1,5})?$", // Optional port - QRegularExpression::UseUnicodePropertiesOption); // Because asian digits + % ServerPartRegEx % '$', + QRegularExpression::UseUnicodePropertiesOption); // Because Asian digits auto match = parser.match(mxidOrDomain); - QUrl maybeBaseUrl = QUrl::fromUserInput(match.captured(2)); + auto maybeBaseUrl = QUrl::fromUserInput(match.captured(2)); maybeBaseUrl.setScheme("https"); // Instead of the Qt-default "http" if (!match.hasMatch() || !maybeBaseUrl.isValid()) { @@ -883,33 +895,36 @@ Room* Connection::room(const QString& roomId, JoinStates states) const Room* Connection::roomByAlias(const QString& roomAlias, JoinStates states) const { - const auto id = d->roomAliasMap.value(roomAlias); + const auto id = + d->roomAliasMap.value(serverPart(roomAlias)).value(roomAlias); if (!id.isEmpty()) return room(id, states); + qCWarning(MAIN) << "Room for alias" << roomAlias << "is not found under account" << userId(); return nullptr; } void Connection::updateRoomAliases(const QString& roomId, + const QString& aliasServer, const QStringList& previousRoomAliases, const QStringList& roomAliases) { + auto& aliasMap = d->roomAliasMap[aliasServer]; // Allocate if necessary for (const auto& a: previousRoomAliases) - if (d->roomAliasMap.remove(a) == 0) + if (aliasMap.remove(a) == 0) qCWarning(MAIN) << "Alias" << a << "is not found (already deleted?)"; for (const auto& a: roomAliases) { - auto& mappedId = d->roomAliasMap[a]; + auto& mappedId = aliasMap[a]; if (!mappedId.isEmpty()) { if (mappedId == roomId) - qCDebug(MAIN) << "Alias" << a << "is already mapped to room" + qCDebug(MAIN) << "Alias" << a << "is already mapped to" << roomId; else - qCWarning(MAIN) << "Alias" << a - << "will be force-remapped from room" + qCWarning(MAIN) << "Alias" << a << "will be force-remapped from" << mappedId << "to" << roomId; } mappedId = roomId; diff --git a/lib/connection.h b/lib/connection.h index cc2feed8..f688c10b 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -250,11 +250,13 @@ namespace QMatrixClient Q_INVOKABLE Room* roomByAlias(const QString& roomAlias, JoinStates states = JoinState::Invite|JoinState::Join) const; /** Update the internal map of room aliases to IDs */ - /// This is used for internal bookkeeping of rooms. Do NOT use - /// it to try change aliases, use Room::setAliases instead + /// This is used to maintain the internal index of room aliases. + /// It does NOT change aliases on the server, + /// \sa Room::setLocalAliases void updateRoomAliases(const QString& roomId, - const QStringList& previousRoomAliases, - const QStringList& roomAliases); + const QString& aliasServer, + const QStringList& previousRoomAliases, + const QStringList& roomAliases); Q_INVOKABLE Room* invitation(const QString& roomId) const; Q_INVOKABLE User* user(const QString& userId); const User* user() const; diff --git a/lib/room.cpp b/lib/room.cpp index 9042130a..06f3490c 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -96,9 +96,14 @@ class Room::Private /// The state of the room at timeline position after-maxTimelineIndex() /// \sa Room::syncEdge QHash currentState; + /// Servers with aliases for this room except the one of the local user + /// \sa Room::remoteAliases + QSet aliasServers; + Timeline timeline; PendingEvents unsyncedEvents; QHash eventsIndex; + QString displayname; Avatar avatar; int highlightCount = 0; @@ -381,9 +386,18 @@ QString Room::name() const return d->getCurrentState()->name(); } -QStringList Room::aliases() const +QStringList Room::localAliases() const { - return d->getCurrentState()->aliases(); + return d->getCurrentState( + connection()->homeserver().authority())->aliases(); +} + +QStringList Room::remoteAliases() const +{ + QStringList result; + for (const auto& s: d->aliasServers) + result += d->getCurrentState(s)->aliases(); + return result; } QString Room::canonicalAlias() const @@ -1624,7 +1638,7 @@ void Room::setCanonicalAlias(const QString& newAlias) d->requestSetState(RoomCanonicalAliasEvent(newAlias)); } -void Room::setAliases(const QStringList& aliases) +void Room::setLocalAliases(const QStringList& aliases) { d->requestSetState(RoomAliasesEvent(aliases)); } @@ -2192,16 +2206,30 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) if (!is(e)) // Room member events are too numerous qCDebug(EVENTS) << "Room state event:" << e; + // clang-format off return visit(e , [] (const RoomNameEvent&) { return NameChange; } , [this,oldStateEvent] (const RoomAliasesEvent& ae) { + // clang-format on + if (ae.aliases().isEmpty()) { + qDebug(MAIN).noquote() << ae.stateKey() + << "no more has aliases for room" << objectName(); + d->aliasServers.remove(ae.stateKey()); + } else { + d->aliasServers.insert(ae.stateKey()); + qDebug(MAIN).nospace().noquote() + << "New server with aliases for room " << objectName() + << ": " << ae.stateKey(); + } const auto previousAliases = oldStateEvent ? static_cast(oldStateEvent)->aliases() : QStringList(); - connection()->updateRoomAliases(id(), previousAliases, ae.aliases()); + connection()->updateRoomAliases(id(), ae.stateKey(), + previousAliases, ae.aliases()); return OtherChange; + // clang-format off } , [this] (const RoomCanonicalAliasEvent& evt) { setObjectName(evt.alias().isEmpty() ? d->id : evt.alias()); @@ -2216,6 +2244,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) return AvatarChange; } , [this,oldStateEvent] (const RoomMemberEvent& evt) { + // clang-format on auto* u = user(evt.userId()); const auto* oldMemberEvent = static_cast(oldStateEvent); @@ -2288,6 +2317,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) d->membersLeft.append(u); } return MembersChange; + // clang-format off } , [this] (const EncryptionEvent&) { emit encryption(); // It can only be done once, so emit it here. @@ -2310,6 +2340,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) return OtherChange; } ); + // clang-format on } Room::Changes Room::processEphemeralEvent(EventPtr&& event) diff --git a/lib/room.h b/lib/room.h index d4a1b959..7c85e4ed 100644 --- a/lib/room.h +++ b/lib/room.h @@ -86,7 +86,8 @@ namespace QMatrixClient Q_PROPERTY(QString predecessorId READ predecessorId NOTIFY baseStateLoaded) Q_PROPERTY(QString successorId READ successorId NOTIFY upgraded) Q_PROPERTY(QString name READ name NOTIFY namesChanged) - Q_PROPERTY(QStringList aliases READ aliases NOTIFY namesChanged) + Q_PROPERTY(QStringList localAliases READ localAliases NOTIFY namesChanged) + Q_PROPERTY(QStringList remoteAliases READ remoteAliases NOTIFY namesChanged) Q_PROPERTY(QString canonicalAlias READ canonicalAlias NOTIFY namesChanged) Q_PROPERTY(QString displayName READ displayName NOTIFY displaynameChanged) Q_PROPERTY(QString topic READ topic NOTIFY topicChanged) @@ -156,7 +157,12 @@ namespace QMatrixClient QString predecessorId() const; QString successorId() const; QString name() const; - QStringList aliases() const; + /// Room aliases defined on the current user's server + /// \sa remoteAliases, setLocalAliases + QStringList localAliases() const; + /// Room aliases defined on other servers + /// \sa localAliases + QStringList remoteAliases() const; QString canonicalAlias() const; QString displayName() const; QString topic() const; @@ -436,7 +442,8 @@ namespace QMatrixClient void discardMessage(const QString& txnId); void setName(const QString& newName); void setCanonicalAlias(const QString& newAlias); - void setAliases(const QStringList& aliases); + /// Set room aliases on the user's current server + void setLocalAliases(const QStringList& aliases); void setTopic(const QString& newTopic); /// You shouldn't normally call this method; it's here for debugging @@ -590,7 +597,6 @@ namespace QMatrixClient void beforeDestruction(Room*); protected: - /// Returns true if any of room names/aliases has changed virtual Changes processStateEvent(const RoomEvent& e); virtual Changes processEphemeralEvent(EventPtr&& event); virtual Changes processAccountDataEvent(EventPtr&& event); -- cgit v1.2.3 From fe82533860a327fbbaa0c980188ccd56d8463b1c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 4 Jul 2019 13:17:36 +0900 Subject: Connection::token() is no more Use Connection::accessToken() instead. --- lib/connection.cpp | 5 ----- lib/connection.h | 2 -- 2 files changed, 7 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 783e12c0..cd02f6d7 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -973,11 +973,6 @@ QString Connection::deviceId() const return d->data->deviceId(); } -QString Connection::token() const -{ - return accessToken(); -} - QByteArray Connection::accessToken() const { return d->data->accessToken(); diff --git a/lib/connection.h b/lib/connection.h index f688c10b..eca3c5be 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -267,8 +267,6 @@ namespace QMatrixClient Q_INVOKABLE SyncJob* syncJob() const; Q_INVOKABLE int millisToReconnect() const; - [[deprecated("Use accessToken() instead")]] - Q_INVOKABLE QString token() const; Q_INVOKABLE void getTurnServers(); struct SupportedRoomVersion -- cgit v1.2.3 From d15ba3d77598c60b1eb713cb2a5348390071db44 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 25 Jun 2019 08:32:15 +0900 Subject: .travis.yml: add "update: true" to homebrew config --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3aaa4039..e0b10ce8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,7 @@ matrix: env: [ 'PATH=/usr/local/opt/qt/bin:$PATH' ] addons: homebrew: + update: true packages: - qt5 -- cgit v1.2.3 From af623d68df8cec92277e9a40f18beac617b5d397 Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Fri, 24 May 2019 15:18:14 +0300 Subject: Add libQtOlm --- .gitmodules | 3 +++ CMakeLists.txt | 39 +++++++++++++++++++++++++++++++++------ cmake/QMatrixClientConfig.cmake | 3 +++ lib/libQtOlm | 1 + 4 files changed, 40 insertions(+), 6 deletions(-) create mode 160000 lib/libQtOlm diff --git a/.gitmodules b/.gitmodules index e69de29b..23158cd2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/libQtOlm"] + path = lib/libQtOlm + url = git@gitlab.com:aa13q/libqtolm.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 92d8a969..6b6cd0f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,6 @@ if (NOT WIN32) endif(NOT WIN32) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -include(cmake/FindOlm.cmake) # Find includes in corresponding build directories set(CMAKE_INCLUDE_CURRENT_DIR ON) @@ -53,8 +52,24 @@ endforeach () # upstream Qt 5.4 is required. find_package(Qt5 5.4.1 REQUIRED Network Gui Multimedia) get_filename_component(Qt5_Prefix "${Qt5_DIR}/../../../.." ABSOLUTE) -find_package(Olm REQUIRED) -get_filename_component(Olm_Prefix "${Olm_INCLUDE_DIRS}/.." ABSOLUTE) + +if ((NOT DEFINED USE_INTREE_LIBQOLM OR USE_INTREE_LIBQOLM) + AND EXISTS ${PROJECT_SOURCE_DIR}/lib/libQtOlm/lib/utils.h) + add_subdirectory(lib/libQtOlm EXCLUDE_FROM_ALL) + include_directories(lib/libQtOlm) + if (NOT DEFINED USE_INTREE_LIBQOLM) + set (USE_INTREE_LIBQOLM 1) + endif () +endif () +if (NOT USE_INTREE_LIBQOLM) + find_package(QtOlm 0.1.0 REQUIRED) + if (NOT QtOlm_FOUND) + message( WARNING "libQtOlm not found; configuration will most likely fail.") + message( WARNING "Make sure you have installed libQtOlm development files") + message( WARNING "as a package or checked out the library sources in lib/.") + message( WARNING "See also BUILDING.md") + endif () +endif () if (GTAD_PATH) get_filename_component(ABS_GTAD_PATH "${GTAD_PATH}" ABSOLUTE) @@ -78,7 +93,20 @@ if (MATRIX_DOC_PATH AND GTAD_PATH) message( STATUS "Generating API stubs enabled" ) message( STATUS " Using GTAD at ${ABS_GTAD_PATH}" ) message( STATUS " Using API files at ${ABS_API_DEF_PATH}" ) - message( STATUS "Using libolm at ${Olm_Prefix}" ) +endif () +find_package(Git) +if (USE_INTREE_LIBQOLM) + message( STATUS "Using in-tree libQtOlm") + if (GIT_FOUND) + execute_process(COMMAND + "${GIT_EXECUTABLE}" rev-parse -q HEAD + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib/libQtOlm/lib + OUTPUT_VARIABLE LIB_GIT_SHA1 + OUTPUT_STRIP_TRAILING_WHITESPACE) + message( STATUS " Library git SHA1: ${LIB_GIT_SHA1}") + endif (GIT_FOUND) +else () + message( STATUS "Using libQtOlm ${QtOlm_VERSION} at ${QtOlm_DIR}") endif () message( STATUS "=============================================================================" ) message( STATUS ) @@ -168,9 +196,8 @@ set_property(TARGET QMatrixClient APPEND PROPERTY target_include_directories(QMatrixClient PUBLIC $ $ - "${Olm_INCLUDE_DIRS}" ) -target_link_libraries(QMatrixClient Qt5::Core Qt5::Network Qt5::Gui Qt5::Multimedia ${Olm_LIBRARIES}) +target_link_libraries(QMatrixClient QtOlm Qt5::Core Qt5::Network Qt5::Gui Qt5::Multimedia) add_executable(qmc-example ${example_SRCS}) target_link_libraries(qmc-example Qt5::Core QMatrixClient) diff --git a/cmake/QMatrixClientConfig.cmake b/cmake/QMatrixClientConfig.cmake index 900038a5..64180cca 100644 --- a/cmake/QMatrixClientConfig.cmake +++ b/cmake/QMatrixClientConfig.cmake @@ -1 +1,4 @@ +include(CMakeFindDependencyMacro) + +find_dependency(QtOlm) include("${CMAKE_CURRENT_LIST_DIR}/QMatrixClientTargets.cmake") diff --git a/lib/libQtOlm b/lib/libQtOlm new file mode 160000 index 00000000..37bebdc9 --- /dev/null +++ b/lib/libQtOlm @@ -0,0 +1 @@ +Subproject commit 37bebdc96aee620df44d211ef91b55b86d8cfbc7 -- cgit v1.2.3 From bb98bd26bdd72998e09553e1703d552d1d217f86 Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Sat, 25 May 2019 03:31:55 +0300 Subject: Add full EncryptionEvent to room logic. Issue #95 --- lib/events/encryptionevent.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++ lib/events/encryptionevent.h | 47 +++++++++++++++++++++++++++++++++++------ lib/events/simplestateevents.h | 2 -- lib/room.cpp | 1 + libqmatrixclient.pri | 2 ++ 5 files changed, 91 insertions(+), 9 deletions(-) diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index 7b620bce..b8e2b575 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -1,5 +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 + +static const std::array encryptionStrings = { { + QStringLiteral("m.megolm.v1.aes-sha2") +} }; + +namespace QMatrixClient { + template <> + struct JsonConverter + { + 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(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 index b44e0eeb..6a4a1c67 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -18,25 +18,58 @@ #pragma once -#include "roomevent.h" +#include "stateevent.h" +#include "eventcontent.h" namespace QMatrixClient { - class EncryptionEvent : public RoomEvent + 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 + { + Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.encryption", EncryptionEvent) - explicit EncryptionEvent(const QJsonObject& obj) - : RoomEvent(typeId(), obj) - , _algorithm(contentJson()["algorithm"].toString()) + using EncryptionType = EncryptionEventContent::EncryptionType; + + explicit EncryptionEvent(const QJsonObject& obj = {}) // TODO: apropriate default value + : StateEvent(typeId(), obj) { } + EncryptionEvent(EncryptionEventContent&& c) + : StateEvent(typeId(), matrixTypeId(), c) + { } + + EncryptionType encryption() const { return content().encryption; } - QString algorithm() const { return _algorithm; } + QString algorithm() const { return content().algorithm; } + int rotationPeriodMs() const { return content().rotationPeriodMs; } + int rotationPeriodMsgs() const { return content().rotationPeriodMsgs; } private: - QString _algorithm; + REGISTER_ENUM(EncryptionType) }; + REGISTER_EVENT_TYPE(EncryptionEvent) DEFINE_EVENTTYPE_ALIAS(Encryption, EncryptionEvent) } // namespace QMatrixClient diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index 2c23d9ca..81401532 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -86,7 +86,5 @@ namespace QMatrixClient 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) } // namespace QMatrixClient diff --git a/lib/room.cpp b/lib/room.cpp index 2ce37acc..14e16850 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" diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index be568bd2..4387af29 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -38,6 +38,7 @@ HEADERS += \ $$SRCPATH/events/callinviteevent.h \ $$SRCPATH/events/accountdataevents.h \ $$SRCPATH/events/directchatevent.h \ + $$SRCPATH/events/encryptionevent.h \ $$SRCPATH/events/redactionevent.h \ $$SRCPATH/events/eventloader.h \ $$SRCPATH/jobs/requestdata.h \ @@ -81,6 +82,7 @@ SOURCES += \ $$SRCPATH/events/callinviteevent.cpp \ $$SRCPATH/events/receiptevent.cpp \ $$SRCPATH/events/directchatevent.cpp \ + $$SRCPATH/events/encryptionevent.cpp \ $$SRCPATH/jobs/requestdata.cpp \ $$SRCPATH/jobs/basejob.cpp \ $$SRCPATH/jobs/syncjob.cpp \ -- cgit v1.2.3 From 46a7350086e42ea3960fe54ef5c68b2207527899 Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Wed, 29 May 2019 02:22:06 +0300 Subject: Update libQtOlm --- lib/libQtOlm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libQtOlm b/lib/libQtOlm index 37bebdc9..5bfc4241 160000 --- a/lib/libQtOlm +++ b/lib/libQtOlm @@ -1 +1 @@ -Subproject commit 37bebdc96aee620df44d211ef91b55b86d8cfbc7 +Subproject commit 5bfc42417d8ee741d2f5a723a2939c895734b92b -- cgit v1.2.3 From 69ba71cca36b4a90328d169c845195f39c041a3a Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Sat, 22 Jun 2019 19:57:18 +0300 Subject: Add EncryptionManager class. Add AccountSettings::encryptionAccountPickle logic. --- CMakeLists.txt | 1 + lib/connection.cpp | 10 ++++ lib/encryptionmanager.cpp | 117 ++++++++++++++++++++++++++++++++++++++++++++++ lib/encryptionmanager.h | 31 ++++++++++++ lib/settings.cpp | 22 ++++++++- lib/settings.h | 5 ++ libqmatrixclient.pri | 2 + 7 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 lib/encryptionmanager.cpp create mode 100644 lib/encryptionmanager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b6cd0f3..d3906ffb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,6 +125,7 @@ set(libqmatrixclient_SRCS lib/networksettings.cpp lib/converters.cpp lib/util.cpp + lib/encryptionmanager.cpp lib/eventitem.cpp lib/events/event.cpp lib/events/roomevent.cpp diff --git a/lib/connection.cpp b/lib/connection.cpp index a7eae30f..d7c3d78f 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" @@ -101,6 +102,8 @@ class Connection::Private GetCapabilitiesJob* capabilitiesJob = nullptr; GetCapabilitiesJob::Capabilities capabilities; + QScopedPointer encryptionManager; + SyncJob* syncJob = nullptr; bool cacheState = true; @@ -235,6 +238,13 @@ 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())); + + d->encryptionManager->uploadIdentityKeys(this); + d->encryptionManager->uploadOneTimeKeys(this); + }); connect(loginJob, &BaseJob::failure, this, [this, loginJob] { diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp new file mode 100644 index 00000000..b40305ea --- /dev/null +++ b/lib/encryptionmanager.cpp @@ -0,0 +1,117 @@ +#include "encryptionmanager.h" + +#include +#include +#include +#include +#include // 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 OlmCurve25519AesSha256AlgoName = QStringLiteral("m.olm.curve25519-aes-sha256"); +static const auto MegolmV1AesShaAlgoName = QStringLiteral("m.megolm.v1.aes-sha"); + +class EncryptionManager::Private +{ + public: + explicit Private(const QByteArray& encryptionAccountPickle, float signedKeysProportion, float oneTimeKeyThreshold) + : olmAccount(new Account(encryptionAccountPickle)), // TODO: passphrase even with qtkeychain? + signedKeysProportion(move(signedKeysProportion)), + oneTimeKeyThreshold(move(oneTimeKeyThreshold)), + targetKeysNumber(olmAccount->maxOneTimeKeys()) // 2 // see note below + { + Q_ASSERT((0 <= signedKeysProportion) && (signedKeysProportion <= 1)); + Q_ASSERT((0 <= oneTimeKeyThreshold) && (oneTimeKeyThreshold <= 1)); + /* + * 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. + */ + } + ~Private() + { + delete olmAccount; + } + + UploadKeysJob* uploadIdentityKeysJob = nullptr; + UploadKeysJob* uploadOneTimeKeysJob = nullptr; + + Account* olmAccount; + const QByteArray encryptionAccountPickle; + + float signedKeysProportion; + float oneTimeKeyThreshold; + int targetKeysNumber; + + void updateKeysToUpload(); + bool oneTimeKeyShouldUpload(); + + QHash oneTimeKeyCounts; + void setOneTimeKeyCounts(const QHash oneTimeKeyCountsNewValue) + { + oneTimeKeyCounts = oneTimeKeyCountsNewValue; + updateKeysToUpload(); + } + QHash oneTimeKeysToUploadCounts; + QHash targetOneTimeKeyCounts + { + {SignedCurve25519Name, qRound(signedKeysProportion * targetKeysNumber)}, + {Curve25519Name, qRound((1-signedKeysProportion) * targetKeysNumber)} + }; +}; + +EncryptionManager::EncryptionManager(const QByteArray &encryptionAccountPickle, float signedKeysProportion, float oneTimeKeyThreshold, + QObject* parent) + : QObject(parent), + d(std::make_unique(std::move(encryptionAccountPickle), std::move(signedKeysProportion), std::move(oneTimeKeyThreshold))) +{ + +} + +EncryptionManager::~EncryptionManager() = default; + +void EncryptionManager::uploadIdentityKeys(Connection* connection) +{ + // TODO +} + +void EncryptionManager::uploadOneTimeKeys(Connection* connection, bool forceUpdate) +{ + // TODO +} + +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..0bd05432 --- /dev/null +++ b/lib/encryptionmanager.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +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, float signedKeysProportion = 1, float oneTimeKeyThreshold = float(0.1), + QObject* parent = nullptr); + ~EncryptionManager(); + + void uploadIdentityKeys(Connection* connection); + void uploadOneTimeKeys(Connection* connection, bool forceUpdate = false); + + private: + class Private; + std::unique_ptr d; + + }; + +} // namespace QMatrixClient 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 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/libqmatrixclient.pri b/libqmatrixclient.pri index 4387af29..dfd45c04 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -13,6 +13,7 @@ INCLUDEPATH += $$SRCPATH HEADERS += \ $$SRCPATH/connectiondata.h \ $$SRCPATH/connection.h \ + $$SRCPATH/encryptionmanager.h \ $$SRCPATH/eventitem.h \ $$SRCPATH/room.h \ $$SRCPATH/user.h \ @@ -61,6 +62,7 @@ HEADERS += \ SOURCES += \ $$SRCPATH/connectiondata.cpp \ $$SRCPATH/connection.cpp \ + $$SRCPATH/encryptionmanager.cpp \ $$SRCPATH/eventitem.cpp \ $$SRCPATH/room.cpp \ $$SRCPATH/user.cpp \ -- cgit v1.2.3 From 79f6f33bbbd60eafe92b5187a3bf5fd8966f8bf5 Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Sat, 22 Jun 2019 19:57:18 +0300 Subject: Upload device public keys. Issue #87 --- lib/encryptionmanager.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index b40305ea..80fdcebd 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -18,6 +18,7 @@ static const auto Curve25519Name = QStringLiteral("curve25519"); static const auto SignedCurve25519Name = QStringLiteral("signed_curve25519"); static const auto OlmCurve25519AesSha256AlgoName = QStringLiteral("m.olm.curve25519-aes-sha256"); static const auto MegolmV1AesShaAlgoName = QStringLiteral("m.megolm.v1.aes-sha"); +static const QStringList SupportedAlgorithms = { OlmCurve25519AesSha256AlgoName, MegolmV1AesShaAlgoName }; class EncryptionManager::Private { @@ -86,7 +87,52 @@ EncryptionManager::~EncryptionManager() = default; void EncryptionManager::uploadIdentityKeys(Connection* connection) { - // TODO + // 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 :. + * 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 for the device key object. + * A map from user ID, to a map from : to the signature. + * The signature is calculated using the process called Signing JSON. + */ + { + { + connection->userId(), + { + { + ed25519Name + QStringLiteral(":") + connection->deviceId(), + d->olmAccount->sign(toJson(deviceKeys)) + } + } + } + } + }; + + connect(d->uploadIdentityKeysJob, &BaseJob::success, this, [this] { + d->setOneTimeKeyCounts(d->uploadIdentityKeysJob->oneTimeKeyCounts()); + qDebug() << QString("Uploaded identity keys."); + }); + d->uploadIdentityKeysJob = connection->callApi(deviceKeys); } void EncryptionManager::uploadOneTimeKeys(Connection* connection, bool forceUpdate) -- cgit v1.2.3 From e1bee9d71d88d6500d2c6124689a0e8685aeab90 Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Sat, 22 Jun 2019 19:57:18 +0300 Subject: Upload one-time keys. Issue #88 --- lib/encryptionmanager.cpp | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 80fdcebd..1e1fc669 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -137,7 +137,50 @@ void EncryptionManager::uploadIdentityKeys(Connection* connection) void EncryptionManager::uploadOneTimeKeys(Connection* connection, bool forceUpdate) { - // TODO + if (forceUpdate || d->oneTimeKeyCounts.isEmpty()) + { + auto job = connection->callApi(); + 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 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(none, oneTimeKeys); + d->olmAccount->markKeysAsPublished(); + qDebug() << QString("Uploaded new one-time keys: %1 signed, %2 unsigned.") + .arg(signedKeysToUploadCount).arg(unsignedKeysToUploadCount); } void EncryptionManager::Private::updateKeysToUpload() -- cgit v1.2.3 From b5f9e1bd20f985c18ec630fa496510018547b728 Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Wed, 26 Jun 2019 16:36:45 +0300 Subject: Change libQtOlm location. Fix .travis.yml and .appveyor.yml --- .appveyor.yml | 3 +++ .gitmodules | 6 +++--- .travis.yml | 10 +++------- 3rdparty/libQtOlm | 1 + CMakeLists.txt | 17 ++++++----------- cmake/FindOlm.cmake | 30 ------------------------------ lib/libQtOlm | 1 - 7 files changed, 16 insertions(+), 52 deletions(-) create mode 160000 3rdparty/libQtOlm delete mode 100644 cmake/FindOlm.cmake delete mode 160000 lib/libQtOlm diff --git a/.appveyor.yml b/.appveyor.yml index 410ad12e..fb5903c1 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -26,6 +26,9 @@ init: before_build: - git submodule update --init --recursive +- cd 3rdparty/libQtOlm +- git clone https://gitlab.matrix.org/matrix-org/olm.git +- cd ../.. - if %MAKETOOL% == cmake cmake -G "NMake Makefiles JOM" -H. -Bbuild -DCMAKE_CXX_FLAGS="/EHsc /W3" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX="%DEPLOY_DIR%" build_script: diff --git a/.gitmodules b/.gitmodules index 23158cd2..eb4c1815 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "lib/libQtOlm"] - path = lib/libQtOlm - url = git@gitlab.com:aa13q/libqtolm.git +[submodule "3rdparty/libQtOlm"] + path = 3rdparty/libQtOlm + url = https://gitlab.com/b0/libqtolm.git diff --git a/.travis.yml b/.travis.yml index 72be46cb..859cabfc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,13 +32,9 @@ before_install: - if [ "$TRAVIS_OS_NAME" = "linux" ]; then USE_NINJA="-GNinja"; VALGRIND="valgrind $VALGRIND_OPTIONS"; . /opt/qt57/bin/qt57-env.sh; fi install: -# olm -- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; else sudo apt-get update -qq; fi -- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install qt5; export PATH="$PATH:/usr/local/opt/qt/bin"; else sudo apt-get install -y qt5-default; fi -- git clone https://matrix.org/git/olm.git && cd olm && make && sudo make install && cd .. -- mkdir build && cd build -- cmake .. -# matrix-doc and gtad +- pushd 3rdparty/libQtOlm +- git clone https://gitlab.matrix.org/matrix-org/olm.git +- popd - git clone https://github.com/QMatrixClient/matrix-doc.git - git clone --recursive https://github.com/KitsuneRal/gtad.git - pushd gtad diff --git a/3rdparty/libQtOlm b/3rdparty/libQtOlm new file mode 160000 index 00000000..f610197b --- /dev/null +++ b/3rdparty/libQtOlm @@ -0,0 +1 @@ +Subproject commit f610197ba38ef87bbab8bcff1053bda684a5994a diff --git a/CMakeLists.txt b/CMakeLists.txt index d3906ffb..19fbdcbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,11 +10,6 @@ if (NOT WIN32) include(GNUInstallDirs) endif(NOT WIN32) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) - -# Find includes in corresponding build directories -set(CMAKE_INCLUDE_CURRENT_DIR ON) - # Instruct CMake to run moc automatically when needed. set(CMAKE_AUTOMOC ON) @@ -54,9 +49,9 @@ find_package(Qt5 5.4.1 REQUIRED Network Gui Multimedia) get_filename_component(Qt5_Prefix "${Qt5_DIR}/../../../.." ABSOLUTE) if ((NOT DEFINED USE_INTREE_LIBQOLM OR USE_INTREE_LIBQOLM) - AND EXISTS ${PROJECT_SOURCE_DIR}/lib/libQtOlm/lib/utils.h) - add_subdirectory(lib/libQtOlm EXCLUDE_FROM_ALL) - include_directories(lib/libQtOlm) + AND EXISTS ${PROJECT_SOURCE_DIR}/3rdparty/libQtOlm/lib/utils.h) + add_subdirectory(3rdparty/libQtOlm EXCLUDE_FROM_ALL) + include_directories(3rdparty/libQtOlm) if (NOT DEFINED USE_INTREE_LIBQOLM) set (USE_INTREE_LIBQOLM 1) endif () @@ -100,10 +95,10 @@ if (USE_INTREE_LIBQOLM) if (GIT_FOUND) execute_process(COMMAND "${GIT_EXECUTABLE}" rev-parse -q HEAD - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib/libQtOlm/lib - OUTPUT_VARIABLE LIB_GIT_SHA1 + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/3rdparty/libQtOlm + OUTPUT_VARIABLE QTOLM_GIT_SHA1 OUTPUT_STRIP_TRAILING_WHITESPACE) - message( STATUS " Library git SHA1: ${LIB_GIT_SHA1}") + message( STATUS " Library git SHA1: ${QTOLM_GIT_SHA1}") endif (GIT_FOUND) else () message( STATUS "Using libQtOlm ${QtOlm_VERSION} at ${QtOlm_DIR}") diff --git a/cmake/FindOlm.cmake b/cmake/FindOlm.cmake deleted file mode 100644 index 3fea7b46..00000000 --- a/cmake/FindOlm.cmake +++ /dev/null @@ -1,30 +0,0 @@ -# - Try to find LibOlm - -# Uses the following variables to help find libolm: -# Olm_INCLUDE_DIR - include files -# Olm_LIBRARY_DIR - libraries -# Once done this will define -# Olm_FOUND - System has olm -# Olm_INCLUDE_DIRS - The olm include directories -# Olm_LIBRARIES - The libraries needed to use olm - -find_path(Olm_INCLUDE_DIRS NAMES - olm/olm.h - olm/inbound_group_session.h - olm/outbound_group_session.h - PATHS "${Olm_INCLUDE_DIR}" - DOC "Path to a directory with libolm header files" -) - -find_library(Olm_LIBRARIES NAMES olm - PATHS "${Olm_LIBRARY_DIR}" - DOC "Path to a directory with libolm libraries" -) - -include(FindPackageHandleStandardArgs) -# handle the QUIETLY and REQUIRED arguments and set OLM_FOUND to TRUE -# if all listed variables are TRUE -find_package_handle_standard_args(olm DEFAULT_MSG - Olm_LIBRARIES Olm_INCLUDE_DIRS) - -mark_as_advanced(Olm_INCLUDE_DIRS Olm_LIBRARIES) diff --git a/lib/libQtOlm b/lib/libQtOlm deleted file mode 160000 index 5bfc4241..00000000 --- a/lib/libQtOlm +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5bfc42417d8ee741d2f5a723a2939c895734b92b -- cgit v1.2.3 From 281e3235d3d4618afd9f01049b8a2acbe1c8475c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 6 Jul 2019 11:41:08 +0900 Subject: Connection::serverPart: replace auto with QString because of QStringBuilder See https://github.com/KDE/clazy/blob/master/docs/checks/README-auto-unexpected-qstringbuilder.md Closes #613. --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index cd02f6d7..5d377173 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -166,7 +166,7 @@ static const auto ServerPartRegEx = QStringLiteral( QString serverPart(const QString& mxId) { - static auto re = "^[@!#$+].+?:(" // Localpart and colon + static QString re = "^[@!#$+].+?:(" // Localpart and colon % ServerPartRegEx % ")$"; static QRegularExpression parser(re, QRegularExpression::UseUnicodePropertiesOption); // Because Asian digits -- cgit v1.2.3 From 9ad25f204fa6caadf93203c42f3ea7de17dceab0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 6 Jul 2019 12:35:45 +0900 Subject: Room::setLocalAliases: now actually working (with a caveat) The caveat is that the library doesn't support .well-known yet, therefore will work not fully correctly (in particular - won't correctly set aliases) with servers that have serverpart different from the homeserver hostname used to connect to it. --- lib/room.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/room.cpp b/lib/room.cpp index 06f3490c..c5367047 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1640,7 +1640,8 @@ void Room::setCanonicalAlias(const QString& newAlias) void Room::setLocalAliases(const QStringList& aliases) { - d->requestSetState(RoomAliasesEvent(aliases)); + d->requestSetState(connection()->homeserver().authority(), + RoomAliasesEvent(aliases)); } void Room::setTopic(const QString& newTopic) -- cgit v1.2.3 From 5b8079ca3d35360b7d98814654f37885f21bcb7e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 6 Jul 2019 19:53:23 +0900 Subject: Fix clazy warnings --- lib/connection.h | 2 +- lib/events/event.h | 2 +- lib/events/eventcontent.h | 8 +++++++- lib/joinstate.h | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/connection.h b/lib/connection.h index eca3c5be..4ab8d5ba 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 diff --git a/lib/events/event.h b/lib/events/event.h index b7bbd83e..5248472c 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -259,7 +259,7 @@ namespace QMatrixClient } template - T content(const QLatin1String& key) const + T content(QLatin1String key) const { return fromJson(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/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 -- cgit v1.2.3 From 01cce0e39d255cbcf39f6a1aa58c6d7ab1d995d1 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 6 Jul 2019 19:58:29 +0900 Subject: Convenience: StateKeyKey, StateKeyKeyL, basicStateEventJson() --- lib/events/event.h | 4 +++- lib/events/roomevent.cpp | 2 +- lib/events/roommemberevent.h | 2 +- lib/events/stateevent.cpp | 2 +- lib/events/stateevent.h | 11 +++++++++++ lib/room.cpp | 2 +- 6 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/events/event.h b/lib/events/event.h index 5248472c..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 inline QJsonObject basicEventJson(StrT matrixType, const QJsonObject& content) diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index 3d03509f..62c2a76d 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -78,7 +78,7 @@ QString RoomEvent::transactionId() const QString RoomEvent::stateKey() const { - return fullJson()["state_key"_ls].toString(); + return fullJson()[StateKeyKeyL].toString(); } void RoomEvent::setTransactionId(const QString& txnId) diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index b8224033..4490fe65 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -76,7 +76,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/stateevent.cpp b/lib/events/stateevent.cpp index a84f302b..476e0fd6 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)) diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 3f54f7bf..5dadac7f 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -21,6 +21,17 @@ #include "roomevent.h" namespace QMatrixClient { + + /// Make a minimal correct Matrix state event JSON + template + inline QJsonObject basicStateEventJson(StrT matrixType, + const QJsonObject& content, const QString& stateKey = {}) + { + return { { TypeKey, std::forward(matrixType) }, + { StateKeyKey, stateKey }, + { ContentKey, content } }; + } + class StateEventBase: public RoomEvent { public: diff --git a/lib/room.cpp b/lib/room.cpp index c5367047..8c9f8760 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1920,7 +1920,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"), -- cgit v1.2.3 From cbb4f219d5c79f81706019c0679222d5ccee4a4c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 6 Jul 2019 19:59:34 +0900 Subject: loadStateEvent() --- lib/events/eventloader.h | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) 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( + basicStateEventJson(matrixType, content, stateKey), matrixType); + } + template struct JsonConverter> { static auto load(const QJsonValue& jv) -- cgit v1.2.3 From 025e5ab7d90ce8cf474567457301de32d5a3e34a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 6 Jul 2019 20:13:38 +0900 Subject: Be stricter on usage of stateKey A few places in the library dealt with state events without any notion of state_key inside events, including StateEvent[Base] and relevant functions in Room. A number of workarounds have been made; e.g., Room::setMemberState() accepted userId as a separate parameter, ignoring the state key inside the RoomMemberEvent already passed to it, and Room::setLocalAliases() had a bug in the initial version where the function still tried to pass aliases in an event with an empty state key. This commit fixes this shortcoming: StateEventBase now gets stateKey as one more parameter, Room::Private::getCurrentState() respects stateKey and returns properly constructed stub events, and Room::setMemberState() gives way to a more generic Room::setState() that works uniformly with whatever state event you pass to it. --- lib/events/roommemberevent.h | 8 +++++- lib/events/simplestateevents.h | 24 ++++++++++++----- lib/events/stateevent.cpp | 6 +++++ lib/events/stateevent.h | 10 +++++-- lib/room.cpp | 60 ++++++++++++++++++++++++++++-------------- lib/room.h | 4 +++ lib/user.cpp | 2 +- 7 files changed, 84 insertions(+), 30 deletions(-) diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index 4490fe65..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 + RoomMemberEvent(const QString& userId, ArgTs&&... contentArgs) + : StateEvent(typeId(), matrixTypeId(), userId, + std::forward(contentArgs)...) { } /// A special constructor to create unknown RoomMemberEvents diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index 2c23d9ca..dc6a0868 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 \ explicit _Name(T&& value) \ - : StateEvent(typeId(), matrixTypeId(), \ + : StateEvent(typeId(), matrixTypeId(), QString(), \ QStringLiteral(#_ContentKey), \ std::forward(value)) \ { } \ @@ -78,9 +76,6 @@ 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) @@ -89,4 +84,21 @@ namespace QMatrixClient DEFINE_SIMPLE_STATE_EVENT(EncryptionEvent, "m.room.encryption", QString, algorithm) DEFINE_EVENTTYPE_ALIAS(RoomEncryption, EncryptionEvent) + + class RoomAliasesEvent + : public StateEvent> + { + 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 476e0fd6..6a6e7782 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -36,6 +36,12 @@ using namespace QMatrixClient; return makeEvent(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 5dadac7f..692f2685 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -37,7 +37,12 @@ namespace QMatrixClient { public: using factory_t = EventFactory; - 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; } @@ -94,8 +99,9 @@ namespace QMatrixClient { } template explicit StateEvent(Type type, event_mtype_t matrixType, + const QString& stateKey = {}, ContentParamTs&&... contentParams) - : StateEventBase(type, matrixType) + : StateEventBase(type, matrixType, stateKey) , _content(std::forward(contentParams)...) { editJson().insert(ContentKey, _content.toJson()); diff --git a/lib/room.cpp b/lib/room.cpp index 8c9f8760..44cd0d06 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -93,6 +93,8 @@ class Room::Private /// The state of the room at timeline position before-0 /// \sa timelineBase std::unordered_map 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 currentState; @@ -193,9 +195,22 @@ class Room::Private template 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(evt); @@ -272,28 +287,26 @@ class Room::Private QString doSendEvent(const RoomEvent* pEvent); void onEventSendingFailure(const QString& txnId, BaseJob* call = nullptr); - template - 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( - id, EvT::matrixTypeId(), stateKey, event.contentJson()); + id, event.matrixType(), + event.stateKey(), event.contentJson()); } qCWarning(MAIN) << q << "has been upgraded, state won't be set"; return nullptr; } - template - auto requestSetState(const EvT& event) + template + auto requestSetState(ArgTs&&... args) { - return connection->callApi( - id, EvT::matrixTypeId(), event.contentJson()); + return requestSetState(EvT(std::forward(args)...)); } - /** + /** * @brief Apply redaction to the timeline * * Tries to find an event in the timeline and redact it; deletes the @@ -317,6 +330,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)) { @@ -1625,28 +1640,32 @@ QString Room::postEvent(RoomEvent* event) QString Room::postJson(const QString& matrixType, const QJsonObject& eventContent) { - return d->sendEvent(loadEvent(basicEventJson(matrixType, eventContent))); + return d->sendEvent(loadEvent(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(newName); } void Room::setCanonicalAlias(const QString& newAlias) { - d->requestSetState(RoomCanonicalAliasEvent(newAlias)); + d->requestSetState(newAlias); } void Room::setLocalAliases(const QStringList& aliases) { - d->requestSetState(connection()->homeserver().authority(), - RoomAliasesEvent(aliases)); + d->requestSetState( + connection()->homeserver().authority(), aliases); } void Room::setTopic(const QString& newTopic) { - d->requestSetState(RoomTopicEvent(newTopic)); + d->requestSetState(newTopic); } bool isEchoEvent(const RoomEventPtr& le, const PendingEventItem& re) @@ -1756,9 +1775,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(memberId, event.content()); } void Room::kickMember(const QString& memberId, const QString& reason) diff --git a/lib/room.h b/lib/room.h index 7c85e4ed..3abf262d 100644 --- a/lib/room.h +++ b/lib/room.h @@ -440,6 +440,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 +456,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 = {}); diff --git a/lib/user.cpp b/lib/user.cpp index 7b695618..8bdcbe97 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); }); } -- cgit v1.2.3 From f58819e4e930ee66e790eccaedf551f807956d72 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 6 Jul 2019 21:01:23 +0900 Subject: Fix building with Clang --- lib/events/stateevent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 692f2685..3b56a265 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -99,7 +99,7 @@ namespace QMatrixClient { } template explicit StateEvent(Type type, event_mtype_t matrixType, - const QString& stateKey = {}, + const QString& stateKey, ContentParamTs&&... contentParams) : StateEventBase(type, matrixType, stateKey) , _content(std::forward(contentParams)...) -- cgit v1.2.3 From a4d1acb8e9e3f101c891f9089a07e214e5cc55f8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 6 Jul 2019 21:03:13 +0900 Subject: .appveyor.yml: stick with static libs --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index fb5903c1..8831b2e3 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -29,7 +29,7 @@ before_build: - cd 3rdparty/libQtOlm - git clone https://gitlab.matrix.org/matrix-org/olm.git - cd ../.. -- if %MAKETOOL% == cmake cmake -G "NMake Makefiles JOM" -H. -Bbuild -DCMAKE_CXX_FLAGS="/EHsc /W3" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX="%DEPLOY_DIR%" +- if %MAKETOOL% == cmake cmake -G "NMake Makefiles JOM" -H. -Bbuild -DCMAKE_CXX_FLAGS="/EHsc /W3" -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX="%DEPLOY_DIR%" build_script: - if %MAKETOOL% == cmake cmake --build build -- cgit v1.2.3 From 4e521ae29a55deaab2ca9d62cddd3791015c6cfb Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 6 Jul 2019 21:58:41 +0900 Subject: EncryptionEvent: Adjust upon merge from master --- lib/events/encryptionevent.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 6a4a1c67..b9e108f0 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -56,8 +56,10 @@ namespace QMatrixClient explicit EncryptionEvent(const QJsonObject& obj = {}) // TODO: apropriate default value : StateEvent(typeId(), obj) { } - EncryptionEvent(EncryptionEventContent&& c) - : StateEvent(typeId(), matrixTypeId(), c) + template + EncryptionEvent(ArgTs&&... contentArgs) + : StateEvent(typeId(), matrixTypeId(), QString(), + std::forward(contentArgs)...) { } EncryptionType encryption() const { return content().encryption; } -- cgit v1.2.3 From d5b4e6440dae82eebc86657dd2f828edaf81b180 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 7 Jul 2019 09:22:14 +0900 Subject: Fix qmake builds in CI --- .appveyor.yml | 14 ++++---------- .travis.yml | 4 ++-- libqmatrixclient.pri | 2 ++ 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 8831b2e3..4e2d4b5d 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -7,16 +7,9 @@ environment: QTDIR: C:\Qt\5.9\msvc2017_64 VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat" PLATFORM: - MAKETOOL: cmake - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - QTDIR: C:\Qt\5.9\msvc2017_64 - VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat" - PLATFORM: - MAKETOOL: qmake - QTDIR: C:\Qt\5.9\msvc2015 VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat" PLATFORM: x86 - MAKETOOL: cmake init: - call "%QTDIR%\bin\qtenv2.bat" @@ -29,11 +22,12 @@ before_build: - cd 3rdparty/libQtOlm - git clone https://gitlab.matrix.org/matrix-org/olm.git - cd ../.. -- if %MAKETOOL% == cmake cmake -G "NMake Makefiles JOM" -H. -Bbuild -DCMAKE_CXX_FLAGS="/EHsc /W3" -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX="%DEPLOY_DIR%" +- cmake -G "NMake Makefiles JOM" -H. -Bbuild -DCMAKE_CXX_FLAGS="/EHsc /W3" -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX="%DEPLOY_DIR%" build_script: -- if %MAKETOOL% == cmake cmake --build build -- if %MAKETOOL% == qmake qmake && jom +- cmake --build build +# qmake uses olm just built by CMake - it can't build olm on its own. +- qmake "INCLUDEPATH += 3rdparty/libQtOlm/olm/include" "LIBS += -Lbuild" && jom #after_build: #- cmake --build build --target install diff --git a/.travis.yml b/.travis.yml index 4654f60c..79d5d0e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,8 +57,8 @@ script: - cmake -DCMAKE_PREFIX_PATH=../install ../examples - cmake --build . --target all - popd -# Build and install with qmake -- qmake qmc-example.pro "CONFIG += debug" "CONFIG -= app_bundle" "QMAKE_CC = $CC" "QMAKE_CXX = $CXX" +# Build with qmake +- qmake qmc-example.pro "CONFIG += debug" "CONFIG -= app_bundle" "QMAKE_CC = $CC" "QMAKE_CXX = $CXX" "INCLUDEPATH += 3rdparty/libQtOlm/olm/include" "LIBS += -Lbuild/lib" - make all # Run the qmake-compiled qmc-example under valgrind - if [ "$QMC_TEST_USER" != "" ]; then $VALGRIND ./qmc-example "$QMC_TEST_USER" "$QMC_TEST_PWD" qmc-example-travis '#qmc-test:matrix.org' "Travis CI job $TRAVIS_JOB_NUMBER"; fi diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index dfd45c04..c561a415 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -7,6 +7,8 @@ win32-msvc* { QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-parameter } +include(3rdparty/libQtOlm/libQtOlm.pri) + SRCPATH = $$PWD/lib INCLUDEPATH += $$SRCPATH -- cgit v1.2.3 From 40002b09f741e5ce1ed618141b2a218438d97084 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 7 Jul 2019 16:47:09 +0900 Subject: Logging categories: libqmatrixclient.* -> quotient.* --- lib/logging.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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") -- cgit v1.2.3 From 3d2b359b6766c008ba28f16737a9faee79043d07 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 7 Jul 2019 16:48:27 +0900 Subject: Documentation: libQMatrixClient -> libQuotient, QMatrixClient -> quotient-im, etc. --- CONTRIBUTING.md | 53 +++++++++--------- ISSUE_TEMPLATE.md | 33 +++++++---- README.md | 161 ++++++++++++++++++++++++++++++++++++------------------ 3 files changed, 155 insertions(+), 92 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 37dfa77e..57edb8d1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,14 +17,14 @@ The long-read part: ## General information For specific proposals, please provide them as -[pull requests](https://github.com/QMatrixClient/libQMatrixClient/pulls) +[pull requests](https://github.com/quotient-im/libQuotient/pulls) or -[issues](https://github.com/QMatrixClient/libqmatrxclient/issues) +[issues](https://github.com/quotient-im/libQuotient/issues) For general discussion, feel free to use our Matrix room: -[#quaternion:matrix.org](https://matrix.to/#/#quaternion:matrix.org). +[#quotient:matrix.org](https://matrix.to/#/#quotient:matrix.org). If you're new to the project (or FLOSS in general), -[issues tagged as easy](https://github.com/QMatrixClient/libQMatrixClient/labels/easy) +[issues tagged as easy](https://github.com/quotient-im/libQuotient/labels/easy) are smaller tasks that may typically take 1-3 days. You are welcome aboard! @@ -44,8 +44,8 @@ and ### How we handle proposals We use GitHub to track all changes via its -[issue tracker](https://github.com/QMatrixClient/libQMatrixClient/issues) and -[pull requests](https://github.com/QMatrixClient/libQMatrixClient/pulls). +[issue tracker](https://github.com/quotient-im/libQuotient/issues) and +[pull requests](https://github.com/quotient-im/libQuotient/pulls). Specific changes are proposed using those mechanisms. Issues are assigned to an individual who works on it and then marks it complete. If there are questions or objections, the conversation area of that @@ -88,7 +88,7 @@ a commit without a DCO is an accident and the DCO still applies. Unless a contributor explicitly specifies otherwise, we assume contributors to agree that all contributed code is released either under *LGPL v2.1 or later*. -This is more than just [LGPL v2.1 libQMatrixClient now uses](./COPYING) +This is more than just [LGPL v2.1 libQuotient now uses](./COPYING) because the project plans to switch to LGPL v3 for library code in the near future. - -- **The client application**: -- **libqmatrixclient version if you know it**: -- **Qt version**: -- **Install method**: -- **Platform**: + + +- **The client application**: + +- **libQuotient version if you know it**: + +- **Qt version**: + +- **Install method**: + +- **Platform**: + diff --git a/README.md b/README.md index 787fa0df..5568101b 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,57 @@ -# libQMatrixClient +# libQuotient (former libQMatrixClient) Made for Matrix -[![license](https://img.shields.io/github/license/QMatrixClient/libqmatrixclient.svg)](https://github.com/QMatrixClient/libqmatrixclient/blob/master/COPYING) +[![license](https://img.shields.io/github/license/quotient-im/libQuotient.svg)](https://github.com/quotient-im/libQuotient/blob/master/COPYING) ![status](https://img.shields.io/badge/status-beta-yellow.svg) -[![release](https://img.shields.io/github/release/QMatrixClient/libqmatrixclient/all.svg)](https://github.com/QMatrixClient/libqmatrixclient/releases/latest) +[![release](https://img.shields.io/github/release/quotient-im/libQuotient/all.svg)](https://github.com/quotient-im/libQuotient/releases/latest) [![](https://img.shields.io/cii/percentage/1023.svg?label=CII%20best%20practices)](https://bestpractices.coreinfrastructure.org/projects/1023/badge) -![](https://img.shields.io/github/commit-activity/y/QMatrixClient/libQMatrixClient.svg) +![](https://img.shields.io/github/commit-activity/y/quotient-im/libQuotient.svg) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) -libQMatrixClient is a Qt5-based library to make IM clients for the [Matrix](https://matrix.org) protocol. It is the backbone of [Quaternion](https://github.com/QMatrixClient/Quaternion), [Spectral](https://matrix.org/docs/projects/client/spectral.html) and some other projects. +The Quotient project aims to produce a Qt5-based SDK to develop applications +for [Matrix](https://matrix.org). libQuotient is a library that enables client +applications. It is the backbone of +[Quaternion](https://github.com/quotient-im/Quaternion), +[Spectral](https://matrix.org/docs/projects/client/spectral.html) and +other projects. +Versions 0.5.x and older use the previous name - libQMatrixClient. ## Contacts -You can find authors of libQMatrixClient in the Matrix room: -[#qmatrixclient:matrix.org](https://matrix.to/#/#qmatrixclient:matrix.org). +You can find Quotient developers in the Matrix room: +[#quotient:matrix.org](https://matrix.to/#/#quotient:matrix.org). -You can also file issues at -[the project's issue tracker](https://github.com/QMatrixClient/libqmatrixclient/issues). +You can file issues at +[the project issue tracker](https://github.com/quotient-im/libQuotient/issues). If you find what looks like a security issue, please use instructions in SECURITY.md. -## Building and usage -So far the library is typically used as a git submodule of another project -(such as Quaternion); however it can be built separately (either as a static or -as a dynamic library). After installing the library the CMake package becomes -available for `find_package(QMatrixClient)` to setup the client code with -the installed library files. PRs to enable the same for qmake are most welcome. - -[The source code is hosted at GitHub](https://github.com/QMatrixClient/libqmatrixclient) - -checking out a certain commit or tag (rather than downloading the archive) is -the recommended way for one-off building. If you want to hack on the library -as a part of another project (e.g. you are working on Quaternion but need -to do some changes to the library code), you're advised to make a recursive -check out of that project (in this case, Quaternion) and update -the library submodule to its master branch. - -Tags consisting of digits and periods represent released versions; tags ending with `-betaN` or `-rcN` mark pre-releases. If/when packaging pre-releases, it is advised to replace a dash with a tilde. +## Getting and using libQuotient +Depending on your platform, the library can come as a separate package. +Recent releases of Debian and OpenSuSE, e.g., already have the package +(under the old name). If your Linux repo doesn't provide binary package +(either libqmatrixclient - older - or libquotient - newer), or you're +on Windows or macOS, your best bet is to build the library from the source +and bundle it with your application. In ### Pre-requisites -- a Linux, macOS or Windows system (desktop versions tried; Ubuntu Touch is known to work; mobile Windows and iOS might work too but never tried) - - For Ubuntu flavours - zesty or later (or a derivative) is good enough out of the box; older ones will need PPAs at least for a newer Qt; in particular, if you have xenial you're advised to add Kubuntu Backports PPA for it -- a Git client to check out this repo -- Qt 5 (either Open Source or Commercial), version 5.6 or higher - (5.9 or higher is strongly recommended) -- a build configuration tool: - - CMake (from your package management system or [the official website](https://cmake.org/download/)) +- A Linux, macOS or Windows system (desktop versions tried; Ubuntu Touch + is known to work; mobile Windows and iOS might work too but never tried) + - For Ubuntu flavours - zesty or later is good enough out of the box; + older ones will need PPAs at least for a newer Qt. In particular, + if you (still) have xenial and cannot upgrade to a newer release + you'll have to add Kubuntu Backports PPA for it. +- Qt 5 (either Open Source or Commercial), 5.9 or higher. +- A build configuration tool: + - CMake (from your package management system or + [the official website](https://cmake.org/download/)) - or qmake (comes with Qt) -- a C++ toolchain supported by your version of Qt (see a link for your platform at [the Qt's platform requirements page](http://doc.qt.io/qt-5/gettingstarted.html#platform-requirements)) - - GCC 5 (Windows, Linux, macOS), Clang 5 (Linux), Apple Clang 8.1 (macOS) and Visual C++ 2015 (Windows) are the oldest officially supported; Clang 3.8 and GCC 4.9.2 are known to still work, maintenance patches for them are accepted - - any build system that works with CMake and/or qmake should be fine: GNU Make, ninja (any platform), NMake, jom (Windows) are known to work. +- A C++ toolchain with C++14 support + - GCC 5 (Windows, Linux, macOS), Clang 5 (Linux), Apple Clang 8.1 (macOS) + and Visual Studio 2017 (Windows) are the oldest officially supported; + Clang 3.8, GCC 4.9.2, VS 2015 may work but not actively maintained. +- Any build system that works with CMake and/or qmake should be fine: + GNU Make, ninja (any platform), NMake, jom (Windows) are known to work. #### Linux Just install things from the list above using your preferred package manager. If your Qt package base is fine-grained you might want to run cmake/qmake and look at error messages. The library is entirely offscreen (QtCore and QtNetwork are essential) but it also depends on QtGui in order to handle avatar thumbnails. @@ -60,35 +63,73 @@ Just install things from the list above using your preferred package manager. If 1. Install Qt5, using their official installer. 1. If you plan to build with CMake, install CMake; if you're ok with qmake, you don't need to install anything on top of Qt. The commands in further sections imply that cmake/qmake is in your PATH - otherwise you have to prepend those commands with actual paths. As an option, it's a good idea to run a `qtenv2.bat` script that can be found in `C:\Qt\\\bin` (assuming you installed Qt to `C:\Qt`); the only thing it does is adding necessary paths to PATH. You might not want to run that script on system startup but it's very handy to setup the environment before building. For CMake, setting `CMAKE_PREFIX_PATH` in the same way as for macOS (see above), also helps. -There are no official MinGW-based 64-bit packages for Qt. If you're determined to build a 64-bit library, either use a Visual Studio toolchain or build Qt5 yourself as described in Qt documentation. - -### Building -#### CMake-based +### Using the library +If you use CMake, `find_package(Quotient)` sets up the client code to use +libQuotient, assuming the library development files are installed. There's no +documented procedure to use a preinstalled library with qmake; consider +introducing a submodule in your source tree and build it along with the rest +of the application for now. Patches to provide .prl files for qmake +are welcome. + +Building with dynamic linkage are only tested on Linux at the moment and are +a recommended way of linking your application with libQuotient on this platform. +Feel free +Static linkage is the default on Windows/macOS; feel free to experiment +with dynamic linking and submit PRs if you get reusable results. + +The example/test application that comes with libQuotient, +[qmc-example](https://github.com/quotient-im/libQuotient/tree/master/examples) +includes most common use cases such as sending messages, uploading files, +setting room state etc.; for more extensive usage check out the source code +of [Quaternion](https://github.com/quotient-im/Quaternion) +(the reference client of Quotient) or [Spectral](https://gitlab.com/b0/spectral). + +To ease the first step, `examples/CMakeLists.txt` is a good starting point +for your own CMake-based project using libQuotient. + +## Building the library +[The source code is at GitHub](https://github.com/quotient-im/libQuotient). +Checking out a certain commit or tag (rather than downloading the archive) +along with submodules is strongly recommended. If you want to hack on +the library as a part of another project (e.g. you are working on Quaternion +but need to do some changes to the library code), it makes sense +to make a recursive check out of that project (in this case, Quaternion) +and update the library submodule (also recursively) to its master branch. + +Tags consisting of digits and periods represent released versions; tags ending +with `-betaN` or `-rcN` mark pre-releases. If/when packaging pre-releases, +it is advised to replace a dash with a tilde. + +### CMake-based In the root directory of the project sources: -``` +```shell script mkdir build_dir cd build_dir cmake .. # Pass -DCMAKE_PREFIX_PATH and -DCMAKE_INSTALL_PREFIX here if needed cmake --build . --target all ``` -This will get you the compiled library in `build_dir` inside your project sources. Static builds are tested on all supported platforms. Dynamic builds of libqmatrixclient are only tested on Linux at the moment; experiments with dynamic builds on Windows/macOS are welcome. Taking a look at [qmc-example](https://github.com/QMatrixClient/libqmatrixclient/tree/master/examples) (used to test the library) should give you a basic idea of using libQMatrixClient; for more extensive usage check out the source code of [Quaternion](https://github.com/QMatrixClient/Quaternion) (the reference client built on QMatrixClient). +This will get you the compiled library in `build_dir` inside your project +sources. Static builds are tested on all supported platforms. You can install the library with CMake: -``` +```shell script cmake --build . --target install ``` -This will also install cmake package config files; once this is done, you can use `examples/CMakeLists.txt` to compile the example with the _installed_ library. This file is a good starting point for your own CMake-based project using libQMatrixClient. -Installation of `qmc-example` application can be skipped by setting `QMATRIXCLIENT_INSTALL_EXAMPLE` to `OFF`. +This will also install cmake package config files; once this is done, you +should be able to use `examples/CMakeLists.txt` to compile qmc-example +with the _installed_ library. Installation of the `qmc-example` binary +along with the rest of the library can be skipped +by setting `QMATRIXCLIENT_INSTALL_EXAMPLE` to `OFF`. -#### qmake-based +### qmake-based The library provides a .pri file with an intention to be included from a bigger project's .pro file. As a starting point you can use `qmc-example.pro` that will build a minimal example of library usage for you. In the root directory of the project sources: -``` +```shell script qmake qmc-example.pro make all ``` This will get you `debug/qmc-example` and `release/qmc-example` console executables that login to the Matrix server at matrix.org with credentials of your choosing (pass the username and password as arguments), run a sync long-polling loop and do some tests of the library API. -Installing the library with qmake is not possible; similarly, a .prl file is not provided. A PR to fix this is welcome. +Installing the standalone library with qmake is not implemented yet. ## Troubleshooting @@ -105,19 +146,33 @@ CMake Warning at CMakeLists.txt:11 (find_package): #### Logging configuration -libqmatrixclient uses Qt's logging categories to make switching certain types of logging easier. In case of troubles at runtime (bugs, crashes) you can increase logging if you add the following to the `QT_LOGGING_RULES` environment variable: +libQuotient uses Qt's logging categories to make switching certain types of logging easier. In case of troubles at runtime (bugs, crashes) you can increase logging if you add the following to the `QT_LOGGING_RULES` environment variable: ``` -libqmatrixclient..= +quotient..= ``` where -- `` is one of: `main`, `jobs`, `jobs.sync`, `events`, `events.ephemeral`, and `profiler` (you can always find the full list in the file `logging.cpp`) +- `` is one of: `main`, `jobs`, `jobs.sync`, `events`, `events.ephemeral`, and `profiler` (you can always find the full list in the file `lib/logging.cpp`) - `` is one of `debug` and `warning` - `` is either `true` or `false`. -`*` can be used as a wildcard for any part between two dots, and comma is used for a separator. Latter statements override former ones, so if you want to switch on all debug logs except `jobs` you can set +`*` can be used as a wildcard for any part between two dots, and semicolon is used for a separator. Latter statements override former ones, so if you want to switch on all debug logs except `jobs` you can set +```shell script +QT_LOGGING_RULES="quotient.*.debug=true;quotient.jobs.debug=false" ``` -QT_LOGGING_RULES="libqmatrixclient.*.debug=true,libqmatrixclient.jobs.debug=false" +Note that `quotient` is a prefix that only works since version 0.6 of +the library; 0.5.x and older used `libqmatrixclient` instead. If you happen +to deal with both libQMatrixClient-era and Quotient-era versions, +it's reasonable to use both prefixes, to make sure you're covered with no +regard to the library version. For example, the above setting could look like +```shell script +QT_LOGGING_RULES="libqmatrixclient.*.debug=true;libqmatrixclient.jobs.debug=false;quotient.*.debug=true;quotient.jobs.debug=false" ``` #### Cache format -In case of troubles with room state and caching it may be useful to switch cache format from binary to JSON. To do that, set the following value in your client's configuration file/registry key (you might need to create the libqmatrixclient key for that): `libqmatrixclient/cache_type` to `json`. This will make cache saving and loading work slightly slower but the cache will be in a text JSON file (very long and unindented so prepare a good JSON viewer or text editor with JSON formatting capabilities). +In case of troubles with room state and caching it may be useful to switch +cache format from binary to JSON. To do that, set the following value in +your client's configuration file/registry key (you might need to create +the libqmatrixclient key for that): `libqmatrixclient/cache_type` to `json`. +This will make cache saving and loading work slightly slower but the cache +will be in a text JSON file (very long and unindented so prepare a good +JSON viewer or text editor with JSON formatting capabilities). -- cgit v1.2.3 From 112632e27fa26fcfbb49886d21704f398cd6a928 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 7 Jul 2019 16:58:02 +0900 Subject: .travis.yml: pass LD_LIBRARY_PATH so that the example could find olm --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 79d5d0e7..de3f62fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -61,7 +61,7 @@ script: - qmake qmc-example.pro "CONFIG += debug" "CONFIG -= app_bundle" "QMAKE_CC = $CC" "QMAKE_CXX = $CXX" "INCLUDEPATH += 3rdparty/libQtOlm/olm/include" "LIBS += -Lbuild/lib" - make all # Run the qmake-compiled qmc-example under valgrind -- if [ "$QMC_TEST_USER" != "" ]; then $VALGRIND ./qmc-example "$QMC_TEST_USER" "$QMC_TEST_PWD" qmc-example-travis '#qmc-test:matrix.org' "Travis CI job $TRAVIS_JOB_NUMBER"; fi +- if [ "$QMC_TEST_USER" != "" ]; then $VALGRIND LD_LIBRARY_PATH="build/lib" ./qmc-example "$QMC_TEST_USER" "$QMC_TEST_PWD" qmc-example-travis '#qmc-test:matrix.org' "Travis CI job $TRAVIS_JOB_NUMBER"; fi notifications: webhooks: -- cgit v1.2.3 From 898906a2ac056ab8837a7307c745c3437feff74e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 7 Jul 2019 16:59:09 +0900 Subject: Optimize Travis CI execution The same code is already in Quaternion's .travis.yml, just copy it here as well. --- .travis.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.travis.yml b/.travis.yml index de3f62fd..880b6260 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,15 @@ language: cpp +git: + depth: false + +before_cache: +- brew cleanup + +cache: + directories: + - $HOME/Library/Caches/Homebrew + addons: apt: sources: -- cgit v1.2.3 From eada787376b9f13e7fdd4e7d127074d5c3b3353e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 7 Jul 2019 17:31:59 +0900 Subject: .travis.yml: Fix a typo in qmc-example invocation --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 880b6260..6880844b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -71,7 +71,7 @@ script: - qmake qmc-example.pro "CONFIG += debug" "CONFIG -= app_bundle" "QMAKE_CC = $CC" "QMAKE_CXX = $CXX" "INCLUDEPATH += 3rdparty/libQtOlm/olm/include" "LIBS += -Lbuild/lib" - make all # Run the qmake-compiled qmc-example under valgrind -- if [ "$QMC_TEST_USER" != "" ]; then $VALGRIND LD_LIBRARY_PATH="build/lib" ./qmc-example "$QMC_TEST_USER" "$QMC_TEST_PWD" qmc-example-travis '#qmc-test:matrix.org' "Travis CI job $TRAVIS_JOB_NUMBER"; fi +- if [ "$QMC_TEST_USER" != "" ]; then LD_LIBRARY_PATH="build/lib" $VALGRIND ./qmc-example "$QMC_TEST_USER" "$QMC_TEST_PWD" qmc-example-travis '#qmc-test:matrix.org' "Travis CI job $TRAVIS_JOB_NUMBER"; fi notifications: webhooks: -- cgit v1.2.3 From 0bfb1c1c69c02c7936cb018ead496616322a1cf7 Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Sun, 7 Jul 2019 21:53:22 +0300 Subject: E2EE: add new account generation logic --- lib/connection.cpp | 3 +++ lib/encryptionmanager.cpp | 37 ++++++++++++++++++++++--------------- lib/encryptionmanager.h | 3 ++- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 20fb367c..ff066def 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -254,6 +254,9 @@ void Connection::doConnectToServer(const QString& user, const QString& password, 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); diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 1e1fc669..a62775d9 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -24,13 +24,17 @@ class EncryptionManager::Private { public: explicit Private(const QByteArray& encryptionAccountPickle, float signedKeysProportion, float oneTimeKeyThreshold) - : olmAccount(new Account(encryptionAccountPickle)), // TODO: passphrase even with qtkeychain? - signedKeysProportion(move(signedKeysProportion)), - oneTimeKeyThreshold(move(oneTimeKeyThreshold)), - targetKeysNumber(olmAccount->maxOneTimeKeys()) // 2 // see note below + : 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: * @@ -42,17 +46,19 @@ class EncryptionManager::Private * 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() - { - delete olmAccount; - } + ~Private() = default; UploadKeysJob* uploadIdentityKeysJob = nullptr; UploadKeysJob* uploadOneTimeKeysJob = nullptr; - Account* olmAccount; - const QByteArray encryptionAccountPickle; + QScopedPointer olmAccount; float signedKeysProportion; float oneTimeKeyThreshold; @@ -68,11 +74,7 @@ class EncryptionManager::Private updateKeysToUpload(); } QHash oneTimeKeysToUploadCounts; - QHash targetOneTimeKeyCounts - { - {SignedCurve25519Name, qRound(signedKeysProportion * targetKeysNumber)}, - {Curve25519Name, qRound((1-signedKeysProportion) * targetKeysNumber)} - }; + QHash targetOneTimeKeyCounts; }; EncryptionManager::EncryptionManager(const QByteArray &encryptionAccountPickle, float signedKeysProportion, float oneTimeKeyThreshold, @@ -183,6 +185,11 @@ void EncryptionManager::uploadOneTimeKeys(Connection* connection, bool forceUpda .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) diff --git a/lib/encryptionmanager.h b/lib/encryptionmanager.h index 0bd05432..40fe7383 100644 --- a/lib/encryptionmanager.h +++ b/lib/encryptionmanager.h @@ -15,12 +15,13 @@ namespace QMatrixClient public: // TODO: store constats separately? // TODO: 0.5 oneTimeKeyThreshold instead of 0.1? - explicit EncryptionManager(const QByteArray& encryptionAccountPickle, float signedKeysProportion = 1, float oneTimeKeyThreshold = float(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; -- cgit v1.2.3 From 8c49ac34c94b53f74c23f67ce3a2720673ab2fac Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Mon, 8 Jul 2019 12:45:27 +0300 Subject: E2EE: fix uploadIdentityKeys signatures generation --- lib/encryptionmanager.cpp | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index a62775d9..c2e244fc 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -112,19 +112,29 @@ void EncryptionManager::uploadIdentityKeys(Connection* connection) d->olmAccount->ed25519IdentityKey() } }, - /* - * Signatures for the device key object. - * A map from user ID, to a map from : to the signature. - * The signature is calculated using the process called Signing JSON. - */ + /* 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 : to the signature. + * The signature is calculated using the process called Signing JSON. + */ + deviceKeys.signatures = + { { + connection->userId(), { - connection->userId(), { - { - ed25519Name + QStringLiteral(":") + connection->deviceId(), - d->olmAccount->sign(toJson(deviceKeys)) - } + ed25519Name + QStringLiteral(":") + connection->deviceId(), + d->olmAccount->sign(deviceKeysJsonObject) } } } -- cgit v1.2.3 From 36f63f398b8a36e0b276e78eceaf3c4123be6b13 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 8 Jul 2019 19:36:27 +0900 Subject: RoomEvent::setRoomId()/setSender() --- lib/events/roomevent.cpp | 10 ++++++++++ lib/events/roomevent.h | 3 +++ 2 files changed, 13 insertions(+) diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index 62c2a76d..f1e563ff 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -81,6 +81,16 @@ QString RoomEvent::stateKey() const 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) { auto unsignedData = fullJson()[UnsignedKeyL].toObject(); 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 -- cgit v1.2.3 From f0c1269bf592f5ba1ccde3b667e6737fe75a335d Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 8 Jul 2019 19:37:23 +0900 Subject: Room: Set roomId and sender on pending events Also: refactoring to streamline state events submission. --- lib/room.cpp | 42 +++++++++++++++++++++++------------------- lib/room.h | 26 ++++++++++++++++---------- lib/user.cpp | 2 +- 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index dea21082..cb368d9e 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -290,15 +290,15 @@ class Room::Private SetRoomStateWithKeyJob* requestSetState(const StateEventBase& event) { - if (q->successorId().isEmpty()) - { - // TODO: Queue up state events sending (see #133). - return connection->callApi( - id, event.matrixType(), - event.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( + id, event.matrixType(), + event.stateKey(), event.contentJson()); } template @@ -1416,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)); @@ -1425,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))); @@ -1630,11 +1639,6 @@ 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)); } @@ -1715,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(callId, lifetime, sdp); } void Room::sendCallCandidates(const QString& callId, const QJsonArray& candidates) { Q_ASSERT(supportsCalls()); - postEvent(new CallCandidatesEvent(callId, candidates)); + d->sendEvent(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(callId, lifetime, sdp); } void Room::answerCall(const QString& callId, const QString& sdp) { Q_ASSERT(supportsCalls()); - postEvent(new CallAnswerEvent(callId, sdp)); + d->sendEvent(callId, sdp); } void Room::hangupCall(const QString& callId) { Q_ASSERT(supportsCalls()); - postEvent(new CallHangupEvent(callId)); + d->sendEvent(callId); } void Room::getPreviousContent(int limit) diff --git a/lib/room.h b/lib/room.h index 3abf262d..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 + auto setState(ArgTs&&... args) const + { + return setState(EvT(std::forward(args)...)); + } + public slots: /** Check whether the room should be upgraded */ void checkVersion(); @@ -481,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/user.cpp b/lib/user.cpp index 8bdcbe97..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->setState(RoomMemberEvent(id(), move(evtC))), + connect(r->setState(id(), move(evtC)), &BaseJob::success, this, [=] { updateName(actualNewName, r); }); } -- cgit v1.2.3 From 36ba515437198659205fceabcdcea0db2d3a6dba Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Mon, 8 Jul 2019 16:40:57 +0300 Subject: E2EE: fix EncryptionManager algorithm names --- lib/encryptionmanager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index c2e244fc..50db9889 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -16,9 +16,9 @@ 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 OlmCurve25519AesSha256AlgoName = QStringLiteral("m.olm.curve25519-aes-sha256"); -static const auto MegolmV1AesShaAlgoName = QStringLiteral("m.megolm.v1.aes-sha"); -static const QStringList SupportedAlgorithms = { OlmCurve25519AesSha256AlgoName, MegolmV1AesShaAlgoName }; +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 { -- cgit v1.2.3 From 651478c1681ba6f93e22c20328a048dbbc263ffe Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 8 Jul 2019 21:31:40 +0900 Subject: Move serverPart() to the public API Also: Connection::resolveServer() now only accepts MXIDs, not domains. --- lib/connection.cpp | 28 +++------------------------- lib/connection.h | 4 ++-- lib/util.cpp | 14 ++++++++++++++ lib/util.h | 8 +++++++- 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index ff066def..6d06c603 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -162,33 +162,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) +void Connection::resolveServer(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); -} - -void Connection::resolveServer(const QString& mxidOrDomain) -{ - // 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") diff --git a/lib/connection.h b/lib/connection.h index 4ab8d5ba..11499a6e 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -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/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 -- cgit v1.2.3