Compare commits

...

59 Commits

Author SHA1 Message Date
vae 6c9f1ab964 feat: wip omemo + key publish 2021-07-22 23:01:30 +03:00
vae 442ad37300 feat: omemo signal lib wip 2021-07-22 20:45:39 +03:00
vae 08fe37bfb2
feat(omemo): basic UI 2021-05-15 12:28:43 +03:00
vae bb2ce750c8
build: add OpenSSL dependency 2021-05-15 00:48:32 +03:00
vae bbeeee4c8a
ref(omemo): reformat qomemo/ 2021-05-13 17:54:37 +03:00
vae 574210f5d9
ref(omemo): bundle separate, add db 2021-05-13 17:51:05 +03:00
vae 2654e38665
feat(omemo): add signal protocol wrappers 2021-05-13 17:50:29 +03:00
vae 12ffe8e8e6
ref(omemo): add qomemo/variant 2021-05-13 00:32:13 +03:00
vae 006752b31c
feat(OMEMO): QXmppClientExtension for OMEMO 2021-05-12 17:33:34 +03:00
vae b1a8f162ce
feat(OMEMO): DeviceKeyStorage, DeviceService, UserDeviceList stubs 2021-05-12 15:50:52 +03:00
vae 6721b62629
feat(OMEMO): qxmppfactories, refactoring 2021-05-12 15:00:41 +03:00
vae b22a4c8ca3
feat(OMEMO): add Device, DeviceList, PreKey, Bundle + XML 2021-05-12 12:33:35 +03:00
vae a6254d88b3
feat: OMEMO devices ui mock 2021-05-12 10:06:14 +03:00
vae 2fbbe1ec22
Merge branch 'build-refactor' into maybe-omemo 2021-05-12 08:30:22 +03:00
vae 8e99cc2969
build: plugins/, passwordStorageEngines/wrappers/ as shared libs 2021-05-12 02:01:02 +03:00
vae 140b0fa6b4
feat: add omemo key list entry WIP 2021-05-12 01:44:47 +03:00
vae cb7e2ede75
feat: add OMEMODevices (WIP) 2021-05-12 00:58:39 +03:00
vae bc66ab7e52
build: signal-protocol-c 2021-05-11 23:17:57 +03:00
vae f94c3dac14 Merge branch 'build-refactor' into maybe-omemo 2021-05-11 23:05:50 +03:00
vae a184ecafa3
build: reformat cmake code 2021-05-11 22:24:55 +03:00
vae 7d2688151c
build: finish up CMakeLists refactoring 2021-05-11 22:21:25 +03:00
vae 0038aca1f6
build: WIP CMakeLists refactoring continue - add FindSignal 2021-05-11 21:35:12 +03:00
vae 6e06a1d5bc
build: WIP CMakeLists refactoring 2021-05-11 20:29:08 +03:00
vae 7d648ab081 feat: add omemo buttons 2021-05-11 19:11:02 +03:00
vae 7c1ae4737e
feat(omemo): WIP signal-protocol-c cpp wrapper 2021-05-11 00:32:44 +03:00
vae 04e745fad4
build: add qomemo dir with TODO 2021-05-11 00:12:33 +03:00
Blue b7b70bc198 segfault fix when trying to send something but the history isn't loaded yet, icon and for attached files which are not previewed 2021-05-11 00:06:40 +03:00
vae 9fbbe0c120
build: add signal-protocol-c dependency 2021-05-10 22:42:17 +03:00
Blue ce047db787 patches from Vae about making libraries static, and about boost, findLMDB CMake script, drop dependency for qtquickcontrols 2021-05-09 02:12:17 +03:00
Blue f45319de25 now instead of storing uploading message in ram I store them in database to be able to recover unsent ones on the next statrt. Found and fixed bug with spam repaints in feedview because of icons 2021-05-07 21:26:02 +03:00
Blue ebf0c64ffb highlight in directory now is optional runtime plugin to KIO, several more file managers to fallback, refactor, 2 new icons 2021-05-06 17:44:43 +03:00
Blue d514db9c4a message context menu began, open and show in folder features 2021-05-04 17:09:41 +03:00
Blue f34289399e text inside of message is selectable again, links are clickable, some refactor, some bugfixes 2021-05-03 14:23:41 +03:00
Blue 05d6761baa a bit of refactor, fix the time to request next portion of messages in ui, fancy shadows are back! 2021-05-03 03:35:43 +03:00
Blue 216dcd29e9 bug fixing, better progres indicator positioning 2021-05-02 02:03:08 +03:00
Blue 0973cb2991 first lousy attempt to make load indicator in feedView 2021-04-30 23:07:00 +03:00
Blue 50190f3eac handled a case when user removes downloaded file, minor optimizations on message changing 2021-04-28 23:26:19 +03:00
Blue b44873d587 pal resourse sticking, notifications of unread messages, new message icons 2021-04-27 22:29:15 +03:00
Blue 4c5efad9dc CMake build error, status icon text tooltip 2021-04-26 19:37:36 +03:00
Blue 8310708c92 First attemtps to upload files, debug, reused of once uploaded or downloaded files 2021-04-23 14:53:48 +03:00
Blue d936c0302d bug with downloads in group chats, status icons in messages, visuals, feedView optimisations 2021-04-23 01:41:32 +03:00
Blue 0e937199b0 debug and actual first way to display pictures in messageFeed 2021-04-21 00:56:47 +03:00
Blue 48e498be25 some debug, message changing in messageFeed 2021-04-20 00:49:24 +03:00
Blue 3a7735b192 First steps on the new idea of file up/downloading 2021-04-18 15:49:20 +03:00
Blue 8f914c02a7 temp url storage commit 2021-04-13 16:27:31 +03:00
Blue 50bb3f5fd7 started progress bars, changed gcc standard to 17 2021-03-22 21:04:26 +03:00
Blue a0348b8fd2 file progress events delivery methonds 2021-02-27 15:21:27 +03:00
Blue 85555da81f just a temp one 2021-02-07 20:02:11 +03:00
Blue ebe5addfb5 just proxying button event from feed view delegate to the feed model 2021-02-06 14:02:42 +03:00
Blue b3c6860e25 seems like i have found solution how to properly render buttons 2021-02-02 01:55:15 +03:00
Blue 00ffbac6b0 initial attempt to paint buttons in the messagefeed 2021-01-14 14:22:02 +03:00
Blue ff4124d1f0 Resolved the bug about crash with an empty history chat 2021-01-12 20:15:21 +03:00
Blue 15342f3c53 self nick in the chat fix, hovering message feature 2021-01-08 00:50:12 +03:00
Blue 270a32db9e achive from the beginning memorizing bugfix, limitation of the requests in the model 2020-08-21 23:57:48 +03:00
Blue e0ef1ef797 Some basic message painting 2020-08-21 00:32:30 +03:00
Blue e1eea2f3a2 made the first prototype, scrolling and word wrapping seems to be working 2020-08-17 13:27:14 +03:00
Blue e54cff0f0c changed my mind, gonna implement the feed on qt instead of qml, first tries, nothing working yet 2020-08-16 00:48:28 +03:00
Blue 4e6bd04b02 experimenting with qml 2020-08-12 19:55:01 +03:00
Blue 38159eafeb first compiling prototype, doesnt work yet 2020-08-12 01:49:51 +03:00
164 changed files with 7943 additions and 1969 deletions

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "external/qxmpp"]
path = external/qxmpp
url = https://github.com/qxmpp-project/qxmpp.git
[submodule "external/signal-protocol-c"]
path = external/signal-protocol-c
url = https://github.com/signalapp/libsignal-protocol-c.git

View File

@ -6,10 +6,14 @@
- requesting the history of the current chat after reconnection
- global availability (in drop down list) gets restored after reconnection
- status icon in active chat changes when presence of the pen pal changes
- infinite progress when open the dialogue with something that has no history to show
### Improvements
- slightly reduced the traffic on the startup by not requesting history of all MUCs
- completely rewritten message feed, now it works way faster
- OPTIONAL RUNTIME dependency: "KIO Widgets" that is supposed to allow you to open a file in your default file manager
- show in folder now is supposed to try it's best to show file in folder, even you don't have KIO installed
- once uploaded local files don't get second time uploaded - the remote URL is reused
## Squawk 0.1.5 (Jul 29, 2020)
### Bug fixes

View File

@ -1,117 +1,119 @@
cmake_minimum_required(VERSION 3.0)
project(squawk)
cmake_minimum_required(VERSION 3.4)
project(squawk VERSION 0.1.6 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_CXX_STANDARD 14)
cmake_policy(SET CMP0076 NEW)
cmake_policy(SET CMP0079 NEW)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
include(GNUInstallDirs)
include_directories(.)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
find_package(Qt5Widgets CONFIG REQUIRED)
find_package(Qt5LinguistTools)
add_executable(squawk)
target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
if(NOT CMAKE_BUILD_TYPE)
option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
option(SYSTEM_SIGNAL "Use system signal-protocol-c lib" ON)
option(WITH_KWALLET "Build KWallet support module" ON)
option(WITH_KIO "Build KIO support module" ON)
# Dependencies
## Qt
find_package(Qt5 COMPONENTS Widgets DBus Gui Xml Network Core REQUIRED)
target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml)
## QXmpp
if (SYSTEM_QXMPP)
find_package(QXmpp CONFIG)
if (NOT QXmpp_FOUND)
set(SYSTEM_QXMPP OFF)
message("QXmpp package wasn't found, trying to build with bundled QXmpp")
else ()
message("Building with system QXmpp")
endif ()
endif ()
if (NOT SYSTEM_QXMPP)
target_link_libraries(squawk PRIVATE qxmpp)
add_subdirectory(external/qxmpp)
else ()
target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
endif ()
# Signal
if (NOT SYSTEM_SIGNAL)
add_subdirectory(external/signal-protocol-c)
add_dependencies(squawk signal-protocol-c)
target_link_libraries(squawk PRIVATE signal-protocol-c)
else ()
find_package(Signal REQUIRED)
target_link_libraries(squawk PRIVATE Signal::Signal)
endif ()
## KIO
if (WITH_KIO)
find_package(KF5KIO CONFIG)
if (NOT KF5KIO_FOUND)
set(WITH_KIO OFF)
message("KIO package wasn't found, KIO support modules wouldn't be built")
else ()
target_compile_definitions(squawk PRIVATE WITH_KIO)
message("Building with support of KIO")
endif ()
endif ()
## KWallet
if (WITH_KWALLET)
find_package(KF5Wallet CONFIG)
if (NOT KF5Wallet_FOUND)
set(WITH_KWALLET OFF)
message("KWallet package wasn't found, KWallet support module wouldn't be built")
else ()
target_compile_definitions(squawk PRIVATE WITH_KWALLET)
message("Building with support of KWallet")
endif ()
endif ()
## LMDB
find_package(LMDB REQUIRED)
target_link_libraries(squawk PRIVATE lmdb)
# OpenSSL
find_package(OpenSSL REQUIRED)
target_link_libraries(squawk PRIVATE OpenSSL::Crypto)
# Misc
target_link_libraries(squawk PRIVATE simpleCrypt)
target_link_libraries(squawk PRIVATE uuid)
# Build type
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
endif ()
set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
message("Build type: ${CMAKE_BUILD_TYPE}")
target_compile_options(squawk PRIVATE
"-Wall;-Wextra"
"$<$<CONFIG:DEBUG>:-g>"
"$<$<CONFIG:RELEASE>:-O3>"
)
set(squawk_SRC
main.cpp
exception.cpp
signalcatcher.cpp
shared/global.cpp
shared/utils.cpp
shared/message.cpp
shared/vcard.cpp
shared/icons.cpp
)
set(squawk_HEAD
exception.h
signalcatcher.h
shared.h
shared/enums.h
shared/message.h
shared/global.h
shared/utils.h
shared/vcard.h
shared/icons.h
)
configure_file(resources/images/logo.svg squawk.svg COPYONLY)
execute_process(COMMAND convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
configure_file(packaging/squawk.desktop squawk.desktop COPYONLY)
set(TS_FILES
translations/squawk.ru.ts
)
qt5_add_translation(QM_FILES ${TS_FILES})
add_custom_target(translations ALL DEPENDS ${QM_FILES})
qt5_add_resources(RCC resources/resources.qrc)
option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
option(WITH_KWALLET "Build KWallet support module" ON)
if (SYSTEM_QXMPP)
find_package(QXmpp CONFIG)
if (NOT QXmpp_FOUND)
set(SYSTEM_QXMPP OFF)
message("QXmpp package wasn't found, trying to build with bundled QXmpp")
else()
message("Building with system QXmpp")
endif()
endif()
if(NOT SYSTEM_QXMPP)
add_subdirectory(external/qxmpp)
endif()
if (WITH_KWALLET)
find_package(KF5Wallet CONFIG)
if (NOT KF5Wallet_FOUND)
set(WITH_KWALLET OFF)
message("KWallet package wasn't found, KWallet support module wouldn't be built")
else()
add_definitions(-DWITH_KWALLET)
message("Building with support of KWallet")
endif()
endif()
add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC})
target_link_libraries(squawk Qt5::Widgets)
add_subdirectory(ui)
add_subdirectory(core)
add_subdirectory(external/simpleCrypt)
target_link_libraries(squawk squawkUI)
target_link_libraries(squawk squawkCORE)
target_link_libraries(squawk uuid)
add_dependencies(${CMAKE_PROJECT_NAME} translations)
add_subdirectory(packaging)
add_subdirectory(plugins)
add_subdirectory(resources)
add_subdirectory(shared)
add_subdirectory(translations)
add_subdirectory(ui)
add_subdirectory(qomemo)
# Install the executable
install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)

View File

@ -13,7 +13,8 @@
- lmdb
- CMake 3.0 or higher
- qxmpp 1.1.0 or higher
- kwallet (optional)
- KDE Frameworks: kwallet (optional)
- KDE Frameworks: KIO (optional)
### Getting
@ -67,6 +68,7 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`.
- `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`)
- `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`)
- `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
- `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`)
## License

47
cmake/FindLMDB.cmake Normal file
View File

@ -0,0 +1,47 @@
#This file is taken from here https://gitlab.ralph.or.at/causal-rt/causal-cpp/, it was GPLv3 license
#Thank you so much, mr. Ralph Alexander Bariz, I hope you don't mind me using your code
# Try to find LMDB headers and library.
#
# Usage of this module as follows:
#
# find_package(LMDB)
#
# Variables used by this module, they can change the default behaviour and need
# to be set before calling find_package:
#
# LMDB_ROOT_DIR Set this variable to the root installation of
# LMDB if the module has problems finding the
# proper installation path.
#
# Variables defined by this module:
#
# LMDB_FOUND System has LMDB library/headers.
# LMDB_LIBRARIES The LMDB library.
# LMDB_INCLUDE_DIRS The location of LMDB headers.
find_path(LMDB_ROOT_DIR
NAMES include/lmdb.h
)
find_library(LMDB_LIBRARIES
NAMES lmdb
HINTS ${LMDB_ROOT_DIR}/lib
)
find_path(LMDB_INCLUDE_DIRS
NAMES lmdb.h
HINTS ${LMDB_ROOT_DIR}/include
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LMDB DEFAULT_MSG
LMDB_LIBRARIES
LMDB_INCLUDE_DIRS
)
mark_as_advanced(
LMDB_ROOT_DIR
LMDB_LIBRARIES
LMDB_INCLUDE_DIRS
)

15
cmake/FindSignal.cmake Normal file
View File

@ -0,0 +1,15 @@
find_path(Signal_INCLUDE_DIR NAMES signal/signal_protocol.h)
find_library(Signal_LIBRARY signal-protocol-c)
mark_as_advanced(Signal_INCLUDE_DIR Signal_LIBRARY)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Signal REQUIRED_VARS Signal_LIBRARY Signal_INCLUDE_DIR)
if (Signal_FOUND AND NOT TARGET Signal::Signal)
add_library(Signal::Signal UNKNOWN IMPORTED)
set_target_properties(Signal::Signal PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION "${Signal_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${Signal_INCLUDE_DIR}"
)
endif ()

View File

@ -1,46 +1,27 @@
cmake_minimum_required(VERSION 3.0)
project(squawkCORE)
set(CMAKE_AUTOMOC ON)
find_package(Qt5Core CONFIG REQUIRED)
find_package(Qt5Gui CONFIG REQUIRED)
find_package(Qt5Network CONFIG REQUIRED)
find_package(Qt5Xml CONFIG REQUIRED)
set(squawkCORE_SRC
squawk.cpp
account.cpp
archive.cpp
rosteritem.cpp
contact.cpp
conference.cpp
storage.cpp
networkaccess.cpp
adapterFuctions.cpp
handlers/messagehandler.cpp
handlers/rosterhandler.cpp
)
target_sources(squawk PRIVATE
account.cpp
account.h
adapterFuctions.cpp
archive.cpp
archive.h
conference.cpp
conference.h
contact.cpp
contact.h
main.cpp
networkaccess.cpp
networkaccess.h
rosteritem.cpp
rosteritem.h
signalcatcher.cpp
signalcatcher.h
squawk.cpp
squawk.h
storage.cpp
storage.h
urlstorage.cpp
urlstorage.h
)
add_subdirectory(handlers)
add_subdirectory(passwordStorageEngines)
# Tell CMake to create the helloworld executable
add_library(squawkCORE ${squawkCORE_SRC})
if(SYSTEM_QXMPP)
get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES)
target_include_directories(squawkCORE PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES})
endif()
# Use the Widgets module from Qt 5.
target_link_libraries(squawkCORE Qt5::Core)
target_link_libraries(squawkCORE Qt5::Network)
target_link_libraries(squawkCORE Qt5::Gui)
target_link_libraries(squawkCORE Qt5::Xml)
target_link_libraries(squawkCORE qxmpp)
target_link_libraries(squawkCORE lmdb)
target_link_libraries(squawkCORE simpleCrypt)
if (WITH_KWALLET)
target_link_libraries(squawkCORE kwalletPSE)
endif()

View File

@ -47,7 +47,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
network(p_net),
passwordType(Shared::AccountPassword::plain),
mh(new MessageHandler(this)),
rh(new RosterHandler(this))
rh(new RosterHandler(this)),
omemo(new QXmpp::Omemo::Manager())
{
config.setUser(p_login);
config.setDomain(p_server);
@ -84,12 +85,14 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
QObject::connect(dm, &QXmppDiscoveryManager::itemsReceived, this, &Account::onDiscoveryItemsReceived);
QObject::connect(dm, &QXmppDiscoveryManager::infoReceived, this, &Account::onDiscoveryInfoReceived);
QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded);
QObject::connect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError);
QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onUploadFileComplete);
QObject::connect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
QObject::connect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
client.addExtension(rcpm);
QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
client.addExtension(omemo.get());
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
path += "/" + name;
@ -155,8 +158,9 @@ Account::~Account()
reconnectTimer->stop();
}
QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded);
QObject::disconnect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError);
QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onUploadFileComplete);
QObject::disconnect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
QObject::disconnect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
delete mh;
delete rh;
@ -402,9 +406,6 @@ QString Core::Account::getFullJid() const {
void Core::Account::sendMessage(const Shared::Message& data) {
mh->sendMessage(data);}
void Core::Account::sendMessage(const Shared::Message& data, const QString& path) {
mh->sendMessage(data, path);}
void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
{
if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
@ -434,13 +435,13 @@ void Core::Account::requestArchive(const QString& jid, int count, const QString&
if (contact == 0) {
qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping";
emit responseArchive(jid, std::list<Shared::Message>());
emit responseArchive(jid, std::list<Shared::Message>(), true);
return;
}
if (state != Shared::ConnectionState::connected) {
qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the account is not online, skipping";
emit responseArchive(contact->jid, std::list<Shared::Message>());
emit responseArchive(contact->jid, std::list<Shared::Message>(), false);
}
contact->requestHistory(count, before);
@ -552,9 +553,11 @@ void Core::Account::onClientError(QXmppClient::Error err)
case QXmppStanza::Error::NotAuthorized:
errorText = "Authentication error";
break;
#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 3, 0)
case QXmppStanza::Error::PaymentRequired:
errorText = "Payment is required";
break;
#endif
case QXmppStanza::Error::RecipientUnavailable:
errorText = "Recipient is unavailable";
break;
@ -909,3 +912,16 @@ void Core::Account::handleDisconnection()
ownVCardRequestInProgress = false;
}
void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& list, bool last)
{
RosterItem* contact = static_cast<RosterItem*>(sender());
qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
if (last) {
qDebug() << "The response contains the first accounted message";
}
emit responseArchive(contact->jid, list, last);
}
void Core::Account::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data){
mh->requestChangeMessage(jid, messageId, data);}

View File

@ -30,23 +30,24 @@
#include <map>
#include <set>
#include <QXmppRosterManager.h>
#include <QXmppCarbonManager.h>
#include <QXmppDiscoveryManager.h>
#include <QXmppMamManager.h>
#include <QXmppMucManager.h>
#include <QXmppClient.h>
#include <QXmppBookmarkManager.h>
#include <QXmppBookmarkSet.h>
#include <QXmppCarbonManager.h>
#include <QXmppClient.h>
#include <QXmppDiscoveryManager.h>
#include <QXmppMamManager.h>
#include <QXmppMessageReceiptManager.h>
#include <QXmppMucManager.h>
#include <QXmppRosterManager.h>
#include <QXmppUploadRequestManager.h>
#include <QXmppVCardIq.h>
#include <QXmppVCardManager.h>
#include <QXmppMessageReceiptManager.h>
#include <qomemo/qxmpp_omemo_manager.h>
#include "shared.h"
#include "contact.h"
#include "conference.h"
#include "contact.h"
#include "networkaccess.h"
#include "shared/shared.h"
#include "handlers/messagehandler.h"
#include "handlers/rosterhandler.h"
@ -88,7 +89,6 @@ public:
void setPasswordType(Shared::AccountPassword pt);
QString getFullJid() const;
void sendMessage(const Shared::Message& data);
void sendMessage(const Shared::Message& data, const QString& path);
void requestArchive(const QString& jid, int count, const QString& before);
void subscribeToContact(const QString& jid, const QString& reason);
void unsubscribeFromContact(const QString& jid, const QString& reason);
@ -97,6 +97,7 @@ public:
void addContactToGroupRequest(const QString& jid, const QString& groupName);
void removeContactFromGroupRequest(const QString& jid, const QString& groupName);
void renameContactRequest(const QString& jid, const QString& newName);
void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
void setRoomJoined(const QString& jid, bool joined);
void setRoomAutoJoin(const QString& jid, bool joined);
@ -127,14 +128,14 @@ signals:
void removePresence(const QString& jid, const QString& name);
void message(const Shared::Message& data);
void changeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
void responseArchive(const QString& jid, const std::list<Shared::Message>& list);
void responseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last);
void error(const QString& text);
void addRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
void changeRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
void removeRoomParticipant(const QString& jid, const QString& nickName);
void receivedVCard(const QString& jid, const Shared::VCard& card);
void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
void uploadFileError(const QString& messageId, const QString& error);
void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
private:
QString name;
@ -165,6 +166,8 @@ private:
MessageHandler* mh;
RosterHandler* rh;
QScopedPointer<QXmpp::Omemo::Manager> omemo;
private slots:
void onClientStateChange(QXmppClient::State state);
@ -183,6 +186,7 @@ private slots:
void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
void onContactHistoryResponse(const std::list<Shared::Message>& list, bool last);
private:
void handleDisconnection();

View File

@ -271,6 +271,8 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
bool hadStanzaId = msg.getStanzaId().size() > 0;
QDateTime oTime = msg.getTime();
bool idChange = msg.change(data);
QDateTime nTime = msg.getTime();
bool orderChange = oTime != nTime;
MDB_val lmdbKey, lmdbData;
QByteArray ba;
@ -280,15 +282,21 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
lmdbKey.mv_size = strId.size();
lmdbKey.mv_data = (char*)strId.c_str();
int rc;
if (idChange) {
rc = mdb_del(txn, main, &lmdbKey, &lmdbData);
if (idChange || orderChange) {
if (idChange) {
rc = mdb_del(txn, main, &lmdbKey, &lmdbData);
} else {
quint64 ostamp = oTime.toMSecsSinceEpoch();
lmdbData.mv_data = (quint8*)&ostamp;
lmdbData.mv_size = 8;
rc = mdb_del(txn, order, &lmdbData, &lmdbKey);
}
if (rc == 0) {
strId = msg.getId().toStdString();
lmdbKey.mv_size = strId.size();
lmdbKey.mv_data = (char*)strId.c_str();
quint64 stamp = oTime.toMSecsSinceEpoch();
quint64 stamp = nTime.toMSecsSinceEpoch();
lmdbData.mv_data = (quint8*)&stamp;
lmdbData.mv_size = 8;
rc = mdb_put(txn, order, &lmdbData, &lmdbKey, 0);
@ -502,8 +510,9 @@ long unsigned int Core::Archive::size() const
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
MDB_stat stat;
mdb_stat(txn, order, &stat);
size_t amount = stat.ms_entries;
mdb_txn_abort(txn);
return stat.ms_entries;
return amount;
}
std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id)
@ -603,10 +612,10 @@ void Core::Archive::setFromTheBeginning(bool is)
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
bool success = setStatValue("beginning", is, txn);
if (success != 0) {
mdb_txn_abort(txn);
} else {
if (success) {
mdb_txn_commit(txn);
} else {
mdb_txn_abort(txn);
}
}
}

View File

@ -25,7 +25,7 @@
#include <QMimeType>
#include "shared/message.h"
#include "exception.h"
#include "shared/exception.h"
#include <lmdb.h>
#include <list>

View File

@ -0,0 +1,6 @@
target_sources(squawk PRIVATE
messagehandler.cpp
messagehandler.h
rosterhandler.cpp
rosterhandler.h
)

View File

@ -23,7 +23,6 @@ Core::MessageHandler::MessageHandler(Core::Account* account):
QObject(),
acc(account),
pendingStateMessages(),
pendingMessages(),
uploadingSlotsQueue()
{
}
@ -168,14 +167,16 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
id = source.id();
#endif
target.setId(id);
if (target.getId().size() == 0) {
QString messageId = target.getId();
if (messageId.size() == 0) {
target.generateRandomId(); //TODO out of desperation, I need at least a random ID
messageId = target.getId();
}
target.setFrom(source.from());
target.setTo(source.to());
target.setBody(source.body());
target.setForwarded(forwarded);
target.setOutOfBandUrl(source.outOfBandUrl());
if (guessing) {
if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) {
outgoing = true;
@ -189,6 +190,12 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
} else {
target.setCurrentTime();
}
QString oob = source.outOfBandUrl();
if (oob.size() > 0) {
target.setAttachPath(acc->network->addMessageAndCheckForPath(oob, acc->getName(), target.getPenPalJid(), messageId));
}
target.setOutOfBandUrl(oob);
}
void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason)
@ -232,11 +239,23 @@ void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString&
}
}
void Core::MessageHandler::sendMessage(Shared::Message data)
void Core::MessageHandler::sendMessage(const Shared::Message& data)
{
if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) {
prepareUpload(data);
} else {
performSending(data);
}
}
void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
{
QString jid = data.getPenPalJid();
QString id = data.getId();
QString oob = data.getOutOfBandUrl();
RosterItem* ri = acc->rh->getRosterItem(jid);
bool sent = false;
QMap<QString, QVariant> changes;
if (acc->state == Shared::ConnectionState::connected) {
QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
@ -245,23 +264,16 @@ void Core::MessageHandler::sendMessage(Shared::Message data)
#endif
msg.setId(id);
msg.setType(static_cast<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible
msg.setOutOfBandUrl(data.getOutOfBandUrl());
msg.setOutOfBandUrl(oob);
msg.setReceiptRequested(true);
bool sent = acc->client.sendPacket(msg);
sent = acc->client.sendPacket(msg);
if (sent) {
data.setState(Shared::Message::State::sent);
} else {
data.setState(Shared::Message::State::error);
data.setErrorText("Couldn't send message via QXMPP library check out logs");
}
if (ri != 0) {
ri->appendMessageToArchive(data);
if (sent) {
pendingStateMessages.insert(std::make_pair(id, jid));
}
data.setErrorText("Couldn't send message: internal QXMPP library error, probably need to check out the logs");
}
} else {
@ -269,41 +281,74 @@ void Core::MessageHandler::sendMessage(Shared::Message data)
data.setErrorText("You are is offline or reconnecting");
}
emit acc->changeMessage(jid, id, {
{"state", static_cast<uint>(data.getState())},
{"errorText", data.getErrorText()}
});
Shared::Message::State mstate = data.getState();
changes.insert("state", static_cast<uint>(mstate));
if (mstate == Shared::Message::State::error) {
changes.insert("errorText", data.getErrorText());
}
if (oob.size() > 0) {
changes.insert("outOfBandUrl", oob);
}
if (!newMessage) {
changes.insert("stamp", data.getTime());
}
if (ri != 0) {
if (newMessage) {
ri->appendMessageToArchive(data);
} else {
ri->changeMessage(id, changes);
}
if (sent) {
pendingStateMessages.insert(std::make_pair(id, jid));
} else {
pendingStateMessages.erase(id);
}
}
emit acc->changeMessage(jid, id, changes);
}
void Core::MessageHandler::sendMessage(const Shared::Message& data, const QString& path)
void Core::MessageHandler::prepareUpload(const Shared::Message& data)
{
if (acc->state == Shared::ConnectionState::connected) {
QString jid = data.getPenPalJid();
QString id = data.getId();
RosterItem* ri = acc->rh->getRosterItem(jid);
if (!ri) {
qDebug() << "An attempt to initialize upload in" << acc->name << "for pal" << jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
return;
}
QString path = data.getAttachPath();
QString url = acc->network->getFileRemoteUrl(path);
if (url.size() != 0) {
sendMessageWithLocalUploadedFile(data, url);
} else {
if (acc->network->isUploading(path, data.getId())) {
pendingMessages.emplace(data.getId(), data);
if (acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) {
ri->appendMessageToArchive(data);
pendingStateMessages.insert(std::make_pair(id, jid));
} else {
if (acc->um->serviceFound()) {
QFileInfo file(path);
if (file.exists() && file.isReadable()) {
uploadingSlotsQueue.emplace_back(path, data);
ri->appendMessageToArchive(data);
pendingStateMessages.insert(std::make_pair(id, jid));
uploadingSlotsQueue.emplace_back(path, id);
if (uploadingSlotsQueue.size() == 1) {
acc->um->requestUploadSlot(file);
}
} else {
onFileUploadError(data.getId(), "Uploading file no longer exists or your system user has no permission to read it");
handleUploadError(jid, id, "Uploading file no longer exists or your system user has no permission to read it");
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
}
} else {
onFileUploadError(data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account");
handleUploadError(jid, id, "Your server doesn't support file upload service, or it's prohibited for your account");
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
}
}
}
} else {
onFileUploadError(data.getId(), "Account is offline or reconnecting");
handleUploadError(data.getPenPalJid(), data.getId(), "Account is offline or reconnecting");
qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping";
}
}
@ -314,12 +359,12 @@ void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slo
if (uploadingSlotsQueue.size() == 0) {
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about success requesting upload slot, but none was requested";
} else {
const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
const QString& mId = pair.second.getId();
acc->network->uploadFile(mId, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
pendingMessages.emplace(mId, pair.second);
uploadingSlotsQueue.pop_front();
const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
const QString& mId = pair.second;
QString palJid = pendingStateMessages.at(mId);
acc->network->uploadFile({acc->name, palJid, mId}, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
uploadingSlotsQueue.pop_front();
if (uploadingSlotsQueue.size() > 0) {
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
}
@ -328,44 +373,111 @@ void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slo
void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request)
{
QString err(request.error().text());
if (uploadingSlotsQueue.size() == 0) {
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested";
qDebug() << request.error().text();
qDebug() << err;
} else {
const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << request.error().text();
emit acc->uploadFileError(pair.second.getId(), "Error requesting slot to upload file: " + request.error().text());
const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << err;
handleUploadError(pendingStateMessages.at(pair.second), pair.second, err);
uploadingSlotsQueue.pop_front();
if (uploadingSlotsQueue.size() > 0) {
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
}
uploadingSlotsQueue.pop_front();
}
}
void Core::MessageHandler::onFileUploaded(const QString& messageId, const QString& url)
void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
{
std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
if (itr != pendingMessages.end()) {
sendMessageWithLocalUploadedFile(itr->second, url);
pendingMessages.erase(itr);
QMap<QString, QVariant> cData = {
{"attachPath", path}
};
for (const Shared::MessageInfo& info : msgs) {
if (info.account == acc->getName()) {
RosterItem* cnt = acc->rh->getRosterItem(info.jid);
if (cnt != 0) {
if (cnt->changeMessage(info.messageId, cData)) {
emit acc->changeMessage(info.jid, info.messageId, cData);
}
}
}
}
}
void Core::MessageHandler::onFileUploadError(const QString& messageId, const QString& errMsg)
void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up)
{
std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
if (itr != pendingMessages.end()) {
pendingMessages.erase(itr);
if (up) {
for (const Shared::MessageInfo& info : msgs) {
if (info.account == acc->getName()) {
handleUploadError(info.jid, info.messageId, text);
}
}
}
}
void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url)
void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText)
{
emit acc->uploadFileError(jid, messageId, "Error requesting slot to upload file: " + errorText);
pendingStateMessages.erase(jid);
requestChangeMessage(jid, messageId, {
{"state", static_cast<uint>(Shared::Message::State::error)},
{"errorText", errorText}
});
}
void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
{
for (const Shared::MessageInfo& info : msgs) {
if (info.account == acc->getName()) {
RosterItem* ri = acc->rh->getRosterItem(info.jid);
if (ri != 0) {
Shared::Message msg = ri->getMessage(info.messageId);
sendMessageWithLocalUploadedFile(msg, path, false);
} else {
qDebug() << "A signal received about complete upload to" << acc->name << "for pal" << info.jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
}
}
}
}
void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage)
{
msg.setOutOfBandUrl(url);
if (msg.getBody().size() == 0) {
msg.setBody(url);
}
sendMessage(msg);
if (msg.getBody().size() == 0) { //not sure why, but most messages do that
msg.setBody(url); //they duplicate oob in body, some of them wouldn't even show an attachment if you don't do that
}
performSending(msg, newMessage);
//TODO removal/progress update
}
static const std::set<QString> allowerToChangeKeys({
"attachPath",
"outOfBandUrl",
"state",
"errorText"
});
void Core::MessageHandler::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data)
{
RosterItem* cnt = acc->rh->getRosterItem(jid);
if (cnt != 0) {
bool allSupported = true;
QString unsupportedString;
for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) { //I need all this madness
if (allowerToChangeKeys.count(itr.key()) != 1) { //to not allow this method
allSupported = false; //to make a message to look like if it was edited
unsupportedString = itr.key(); //basically I needed to control who exaclty calls this method
break; //because the underlying tech assumes that the change is initiated by user
} //not by system
}
if (allSupported) {
cnt->changeMessage(messageId, data);
emit acc->changeMessage(jid, messageId, data);
} else {
qDebug() << "A request to change message" << messageId << "of conversation" << jid << "with following data" << data;
qDebug() << "only limited set of dataFields are supported yet here, and" << unsupportedString << "isn't one of them, skipping";
}
}
}

View File

@ -28,6 +28,7 @@
#include <QXmppHttpUploadIq.h>
#include <shared/message.h>
#include <shared/messageinfo.h>
namespace Core {
@ -44,8 +45,7 @@ public:
MessageHandler(Account* account);
public:
void sendMessage(Shared::Message data);
void sendMessage(const Shared::Message& data, const QString& path);
void sendMessage(const Shared::Message& data);
void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
public slots:
@ -55,20 +55,24 @@ public slots:
void onReceiptReceived(const QString& jid, const QString& id);
void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
void onFileUploaded(const QString& messageId, const QString& url);
void onFileUploadError(const QString& messageId, const QString& errMsg);
void onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
void onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& path, bool up);
void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
private:
bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url);
void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage = true);
void performSending(Shared::Message data, bool newMessage = true);
void prepareUpload(const Shared::Message& data);
void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText);
private:
Account* acc;
std::map<QString, QString> pendingStateMessages;
std::map<QString, Shared::Message> pendingMessages;
std::deque<std::pair<QString, Shared::Message>> uploadingSlotsQueue;
std::map<QString, QString> pendingStateMessages; //key is message id, value is JID
std::deque<std::pair<QString, QString>> uploadingSlotsQueue;
};
}

View File

@ -190,7 +190,7 @@ void Core::RosterHandler::removeContactRequest(const QString& jid)
void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact)
{
connect(contact, &RosterItem::needHistory, this->acc, &Account::onContactNeedHistory);
connect(contact, &RosterItem::historyResponse, this, &RosterHandler::onContactHistoryResponse);
connect(contact, &RosterItem::historyResponse, this->acc, &Account::onContactHistoryResponse);
connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged);
connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged);
connect(contact, &RosterItem::requestVCard, this->acc, &Account::requestVCard);
@ -315,14 +315,6 @@ void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& gro
}
}
void Core::RosterHandler::onContactHistoryResponse(const std::list<Shared::Message>& list)
{
RosterItem* contact = static_cast<RosterItem*>(sender());
qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
emit acc->responseArchive(contact->jid, list);
}
Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid)
{
RosterItem* item = 0;

View File

@ -86,7 +86,6 @@ private slots:
void onContactGroupRemoved(const QString& group);
void onContactNameChanged(const QString& name);
void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
void onContactHistoryResponse(const std::list<Shared::Message>& list);
void onContactAvatarChanged(Shared::Avatar, const QString& path);
private:

View File

@ -16,23 +16,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ui/squawk.h"
#include "core/squawk.h"
#include "../shared/global.h"
#include "../shared/messageinfo.h"
#include "../ui/squawk.h"
#include "signalcatcher.h"
#include "shared/global.h"
#include <QtWidgets/QApplication>
#include <QtCore/QThread>
#include <QtCore/QObject>
#include <QSettings>
#include <QTranslator>
#include "squawk.h"
#include <QLibraryInfo>
#include <QSettings>
#include <QStandardPaths>
#include <QTranslator>
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
qRegisterMetaType<Shared::Message>("Shared::Message");
qRegisterMetaType<Shared::MessageInfo>("Shared::MessageInfo");
qRegisterMetaType<Shared::VCard>("Shared::VCard");
qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::MessageInfo>");
qRegisterMetaType<QSet<QString>>("QSet<QString>");
qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
qRegisterMetaType<Shared::Availability>("Shared::Availability");
@ -96,10 +99,7 @@ int main(int argc, char *argv[])
QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount);
QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount);
QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState);
QObject::connect(&w, qOverload<const QString&, const Shared::Message&>(&Squawk::sendMessage),
squawk, qOverload<const QString&, const Shared::Message&>(&Core::Squawk::sendMessage));
QObject::connect(&w, qOverload<const QString&, const Shared::Message&, const QString&>(&Squawk::sendMessage),
squawk, qOverload<const QString&, const Shared::Message&, const QString&>(&Core::Squawk::sendMessage));
QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage);
QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive);
QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact);
QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact);
@ -109,14 +109,14 @@ int main(int argc, char *argv[])
QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin);
QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest);
QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest);
QObject::connect(&w, &Squawk::fileLocalPathRequest, squawk, &Core::Squawk::fileLocalPathRequest);
QObject::connect(&w, &Squawk::downloadFileRequest, squawk, &Core::Squawk::downloadFileRequest);
QObject::connect(&w, &Squawk::fileDownloadRequest, squawk, &Core::Squawk::fileDownloadRequest);
QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest);
QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest);
QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard);
QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard);
QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword);
QObject::connect(&w, &Squawk::localPathInvalid, squawk, &Core::Squawk::onLocalPathInvalid);
QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount);
QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact);
@ -141,11 +141,10 @@ int main(int argc, char *argv[])
QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant);
QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant);
QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant);
QObject::connect(squawk, &Core::Squawk::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse);
QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::fileProgress);
QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::fileError);
QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::fileProgress);
QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::fileError);
QObject::connect(squawk, &Core::Squawk::fileDownloadComplete, &w, &Squawk::fileDownloadComplete);
QObject::connect(squawk, &Core::Squawk::fileUploadComplete, &w, &Squawk::fileUploadComplete);
QObject::connect(squawk, &Core::Squawk::fileProgress, &w, &Squawk::fileProgress);
QObject::connect(squawk, &Core::Squawk::fileError, &w, &Squawk::fileError);
QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword);
QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings);

View File

@ -16,13 +16,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QtWidgets/QApplication>
#include <QtCore/QDir>
#include "networkaccess.h"
Core::NetworkAccess::NetworkAccess(QObject* parent):
QObject(parent),
running(false),
manager(0),
files("files"),
storage("fileURLStorage"),
downloads(),
uploads()
{
@ -33,60 +37,31 @@ Core::NetworkAccess::~NetworkAccess()
stop();
}
void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const QString& url)
void Core::NetworkAccess::downladFile(const QString& url)
{
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
if (itr != downloads.end()) {
Transfer* dwn = itr->second;
std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
if (mItr == dwn->messages.end()) {
dwn->messages.insert(messageId);
}
emit downloadFileProgress(messageId, dwn->progress);
qDebug() << "NetworkAccess received a request to download a file" << url << ", but the file is currently downloading, skipping";
} else {
try {
QString path = files.getRecord(url);
QFileInfo info(path);
if (info.exists() && info.isFile()) {
emit fileLocalPathResponse(messageId, path);
std::pair<QString, std::list<Shared::MessageInfo>> p = storage.getPath(url);
if (p.first.size() > 0) {
QFileInfo info(p.first);
if (info.exists() && info.isFile()) {
emit downloadFileComplete(p.second, p.first);
} else {
startDownload(p.second, url);
}
} else {
files.removeRecord(url);
emit fileLocalPathResponse(messageId, "");
startDownload(p.second, url);
}
} catch (const Archive::NotFound& e) {
emit fileLocalPathResponse(messageId, "");
qDebug() << "NetworkAccess received a request to download a file" << url << ", but there is now record of which message uses that file, downloading anyway";
storage.addFile(url);
startDownload(std::list<Shared::MessageInfo>(), url);
} catch (const Archive::Unknown& e) {
qDebug() << "Error requesting file path:" << e.what();
emit fileLocalPathResponse(messageId, "");
}
}
}
void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QString& url)
{
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
if (itr != downloads.end()) {
Transfer* dwn = itr->second;
std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
if (mItr == dwn->messages.end()) {
dwn->messages.insert(messageId);
}
emit downloadFileProgress(messageId, dwn->progress);
} else {
try {
QString path = files.getRecord(url);
QFileInfo info(path);
if (info.exists() && info.isFile()) {
emit fileLocalPathResponse(messageId, path);
} else {
files.removeRecord(url);
startDownload(messageId, url);
}
} catch (const Archive::NotFound& e) {
startDownload(messageId, url);
} catch (const Archive::Unknown& e) {
qDebug() << "Error requesting file path:" << e.what();
emit downloadFileError(messageId, QString("Database error: ") + e.what());
emit loadFileError(std::list<Shared::MessageInfo>(), QString("Database error: ") + e.what(), false);
}
}
}
@ -95,7 +70,7 @@ void Core::NetworkAccess::start()
{
if (!running) {
manager = new QNetworkAccessManager();
files.open();
storage.open();
running = true;
}
}
@ -103,7 +78,7 @@ void Core::NetworkAccess::start()
void Core::NetworkAccess::stop()
{
if (running) {
files.close();
storage.close();
manager->deleteLater();
manager = 0;
running = false;
@ -128,9 +103,7 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
qreal total = bytesTotal;
qreal progress = received/total;
dwn->progress = progress;
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
emit downloadFileProgress(*mItr, progress);
}
emit loadFileProgress(dwn->messages, progress, false);
}
}
@ -146,9 +119,7 @@ void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
if (errorText.size() > 0) {
itr->second->success = false;
Transfer* dwn = itr->second;
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
emit downloadFileError(*mItr, errorText);
}
emit loadFileError(dwn->messages, errorText, false);
}
}
}
@ -276,61 +247,54 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
void Core::NetworkAccess::onDownloadFinished()
{
QString path("");
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
if (itr == downloads.end()) {
qDebug() << "an error downloading" << url << ": the request is done but seems like noone is waiting for it, skipping";
qDebug() << "an error downloading" << url << ": the request is done but there is no record of it being downloaded, ignoring";
} else {
Transfer* dwn = itr->second;
if (dwn->success) {
qDebug() << "download success for" << url;
QStringList hops = url.split("/");
QString fileName = hops.back();
QStringList parts = fileName.split(".");
path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/";
QString suffix("");
QStringList::const_iterator sItr = parts.begin();
QString realName = *sItr;
++sItr;
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
suffix += "." + (*sItr);
QString jid;
if (dwn->messages.size() > 0) {
jid = dwn->messages.front().jid;
}
QString postfix("");
QFileInfo proposedName(path + realName + postfix + suffix);
int counter = 0;
while (proposedName.exists()) {
postfix = QString("(") + std::to_string(++counter).c_str() + ")";
proposedName = QFileInfo(path + realName + postfix + suffix);
QString path = prepareDirectory(jid);
if (path.size() > 0) {
path = checkFileName(fileName, path);
QFile file(path);
if (file.open(QIODevice::WriteOnly)) {
file.write(dwn->reply->readAll());
file.close();
storage.setPath(url, path);
qDebug() << "file" << path << "was successfully downloaded";
} else {
qDebug() << "couldn't save file" << path;
path = QString();
}
}
path = proposedName.absoluteFilePath();
QFile file(path);
if (file.open(QIODevice::WriteOnly)) {
file.write(dwn->reply->readAll());
file.close();
files.addRecord(url, path);
qDebug() << "file" << path << "was successfully downloaded";
if (path.size() > 0) {
emit downloadFileComplete(dwn->messages, path);
} else {
qDebug() << "couldn't save file" << path;
path = "";
//TODO do I need to handle the failure here or it's already being handled in error?
//emit loadFileError(dwn->messages, path, false);
}
}
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
emit fileLocalPathResponse(*mItr, path);
}
dwn->reply->deleteLater();
delete dwn;
downloads.erase(itr);
}
}
void Core::NetworkAccess::startDownload(const QString& messageId, const QString& url)
void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url)
{
Transfer* dwn = new Transfer({{messageId}, 0, 0, true, "", url, 0});
Transfer* dwn = new Transfer({msgs, 0, 0, true, "", url, 0});
QNetworkRequest req(url);
dwn->reply = manager->get(req);
connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
@ -341,7 +305,7 @@ void Core::NetworkAccess::startDownload(const QString& messageId, const QString&
#endif
connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onDownloadFinished);
downloads.insert(std::make_pair(url, dwn));
emit downloadFileProgress(messageId, 0);
emit loadFileProgress(dwn->messages, 0, false);
}
void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
@ -350,16 +314,16 @@ void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
QString url = rpl->url().toString();
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
if (itr == uploads.end()) {
qDebug() << "an error uploading" << url << ": the request is reporting an error but seems like noone is waiting for it, skipping";
qDebug() << "an error uploading" << url << ": the request is reporting an error but there is no record of it being uploading, ignoring";
} else {
QString errorText = getErrorText(code);
if (errorText.size() > 0) {
itr->second->success = false;
Transfer* upl = itr->second;
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
emit uploadFileError(*mItr, errorText);
}
emit loadFileError(upl->messages, errorText, true);
}
//TODO deletion?
}
}
@ -369,17 +333,14 @@ void Core::NetworkAccess::onUploadFinished()
QString url = rpl->url().toString();
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
if (itr == downloads.end()) {
qDebug() << "an error uploading" << url << ": the request is done but seems like no one is waiting for it, skipping";
qDebug() << "an error uploading" << url << ": the request is done there is no record of it being uploading, ignoring";
} else {
Transfer* upl = itr->second;
if (upl->success) {
qDebug() << "upload success for" << url;
files.addRecord(upl->url, upl->path);
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
emit fileLocalPathResponse(*mItr, upl->path);
emit uploadFileComplete(*mItr, upl->url);
}
storage.addFile(upl->messages, upl->url, upl->path);
emit uploadFileComplete(upl->messages, upl->url);
}
upl->reply->deleteLater();
@ -403,94 +364,29 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot
qreal total = bytesTotal;
qreal progress = received/total;
upl->progress = progress;
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
emit uploadFileProgress(*mItr, progress);
}
}
}
void Core::NetworkAccess::startUpload(const QString& messageId, const QString& url, const QString& path)
{
Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, url, 0});
QNetworkRequest req(url);
QFile* file = new QFile(path);
if (file->open(QIODevice::ReadOnly)) {
upl->reply = manager->put(req, file);
connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onUploadError);
#else
connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onUploadError);
#endif
connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
uploads.insert(std::make_pair(url, upl));
emit downloadFileProgress(messageId, 0);
} else {
qDebug() << "couldn't upload file" << path;
emit uploadFileError(messageId, "Error opening file");
delete file;
}
}
void Core::NetworkAccess::uploadFileRequest(const QString& messageId, const QString& url, const QString& path)
{
std::map<QString, Transfer*>::iterator itr = uploads.find(url);
if (itr != uploads.end()) {
Transfer* upl = itr->second;
std::set<QString>::const_iterator mItr = upl->messages.find(messageId);
if (mItr == upl->messages.end()) {
upl->messages.insert(messageId);
}
emit uploadFileProgress(messageId, upl->progress);
} else {
try {
QString ePath = files.getRecord(url);
if (ePath == path) {
QFileInfo info(path);
if (info.exists() && info.isFile()) {
emit fileLocalPathResponse(messageId, path);
} else {
files.removeRecord(url);
startUpload(messageId, url, path);
}
} else {
QFileInfo info(path);
if (info.exists() && info.isFile()) {
files.changeRecord(url, path);
emit fileLocalPathResponse(messageId, path);
} else {
files.removeRecord(url);
startUpload(messageId, url, path);
}
}
} catch (const Archive::NotFound& e) {
startUpload(messageId, url, path);
} catch (const Archive::Unknown& e) {
qDebug() << "Error requesting file path on upload:" << e.what();
emit uploadFileError(messageId, QString("Database error: ") + e.what());
}
emit loadFileProgress(upl->messages, progress, true);
}
}
QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
{
return ""; //TODO this is a way not to upload some file more then 1 time, here I'm supposed to return that file GET url
QString p;
try {
p = storage.getUrl(path);
} catch (const Archive::NotFound& err) {
} catch (...) {
throw;
}
return p;
}
bool Core::NetworkAccess::isUploading(const QString& path, const QString& messageId)
{
return false; //TODO this is a way to avoid parallel uploading of the same files by different chats
// message is is supposed to be added to the uploading messageids list
// the result should be true if there was an uploading file with this path
// message id can be empty, then it's just to check and not to add
}
void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
void Core::NetworkAccess::uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
{
QFile* file = new QFile(path);
Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, get.toString(), file});
Transfer* upl = new Transfer({{info}, 0, 0, true, path, get.toString(), file});
QNetworkRequest req(put);
for (QMap<QString, QString>::const_iterator itr = headers.begin(), end = headers.end(); itr != end; itr++) {
req.setRawHeader(itr.key().toUtf8(), itr.value().toUtf8());
@ -506,10 +402,99 @@ void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& pa
#endif
connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
uploads.insert(std::make_pair(put.toString(), upl));
emit downloadFileProgress(messageId, 0);
emit loadFileProgress(upl->messages, 0, true);
} else {
qDebug() << "couldn't upload file" << path;
emit uploadFileError(messageId, "Error opening file");
emit loadFileError(upl->messages, "Error opening file", true);
delete file;
delete upl;
}
}
void Core::NetworkAccess::registerFile(const QString& url, const QString& account, const QString& jid, const QString& id)
{
storage.addFile(url, account, jid, id);
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
if (itr != downloads.end()) {
itr->second->messages.emplace_back(account, jid, id); //TODO notification is going to happen the next tick, is that okay?
}
}
void Core::NetworkAccess::registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
{
storage.addFile(url, path, account, jid, id);
}
bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path)
{
for (const std::pair<const QString, Transfer*>& pair : uploads) {
Transfer* info = pair.second;
if (pair.second->path == path) {
std::list<Shared::MessageInfo>& messages = info->messages;
bool dup = false;
for (const Shared::MessageInfo& info : messages) {
if (info.account == acc && info.jid == jid && info.messageId == id) {
dup = true;
break;
}
}
if (!dup) {
info->messages.emplace_back(acc, jid, id); //TODO notification is going to happen the next tick, is that okay?
return true;
}
}
}
return false;
}
QString Core::NetworkAccess::prepareDirectory(const QString& jid)
{
QString path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
path += "/" + QApplication::applicationName();
if (jid.size() > 0) {
path += "/" + jid;
}
QDir location(path);
if (!location.exists()) {
bool res = location.mkpath(path);
if (!res) {
return "";
} else {
return path;
}
}
return path;
}
QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path)
{
QStringList parts = name.split(".");
QString suffix("");
QStringList::const_iterator sItr = parts.begin();
QString realName = *sItr;
++sItr;
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
suffix += "." + (*sItr);
}
QString postfix("");
QFileInfo proposedName(path + "/" + realName + suffix);
int counter = 0;
while (proposedName.exists()) {
QString count = QString("(") + std::to_string(++counter).c_str() + ")";
proposedName = QFileInfo(path + "/" + realName + count + suffix);
}
return proposedName.absoluteFilePath();
}
QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
{
return storage.addMessageAndCheckForPath(url, account, jid, id);
}
std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QString& path)
{
return storage.deletedFile(path);
}

View File

@ -29,13 +29,15 @@
#include <set>
#include "storage.h"
#include "urlstorage.h"
namespace Core {
/**
* @todo write docs
*/
//TODO Need to describe how to get rid of records when file is no longer reachable;
class NetworkAccess : public QObject
{
Q_OBJECT
@ -48,26 +50,27 @@ public:
void stop();
QString getFileRemoteUrl(const QString& path);
bool isUploading(const QString& path, const QString& messageId = "");
void uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers);
QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
void uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers);
bool checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path);
std::list<Shared::MessageInfo> reportPathInvalid(const QString& path);
signals:
void fileLocalPathResponse(const QString& messageId, const QString& path);
void downloadFileProgress(const QString& messageId, qreal value);
void downloadFileError(const QString& messageId, const QString& path);
void uploadFileProgress(const QString& messageId, qreal value);
void uploadFileError(const QString& messageId, const QString& path);
void uploadFileComplete(const QString& messageId, const QString& url);
void loadFileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
void loadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up);
void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url);
void downloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
public slots:
void fileLocalPathRequest(const QString& messageId, const QString& url);
void downladFileRequest(const QString& messageId, const QString& url);
void uploadFileRequest(const QString& messageId, const QString& url, const QString& path);
void downladFile(const QString& url);
void registerFile(const QString& url, const QString& account, const QString& jid, const QString& id);
void registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
private:
void startDownload(const QString& messageId, const QString& url);
void startUpload(const QString& messageId, const QString& url, const QString& path);
void startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url);
QString getErrorText(QNetworkReply::NetworkError code);
QString prepareDirectory(const QString& jid);
QString checkFileName(const QString& name, const QString& path);
private slots:
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
@ -80,12 +83,12 @@ private slots:
private:
bool running;
QNetworkAccessManager* manager;
Storage files;
UrlStorage storage;
std::map<QString, Transfer*> downloads;
std::map<QString, Transfer*> uploads;
struct Transfer {
std::set<QString> messages;
std::list<Shared::MessageInfo> messages;
qreal progress;
QNetworkReply* reply;
bool success;

View File

@ -1,37 +1,9 @@
cmake_minimum_required(VERSION 3.0)
project(pse)
if (WITH_KWALLET)
set(CMAKE_AUTOMOC ON)
find_package(Qt5Core CONFIG REQUIRED)
find_package(Qt5Gui CONFIG REQUIRED)
get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES)
get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES)
set(kwalletPSE_SRC
kwallet.cpp
)
add_library(kwalletPSE ${kwalletPSE_SRC})
target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
target_include_directories(kwalletPSE PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
target_link_libraries(kwalletPSE Qt5::Core)
set(kwalletW_SRC
wrappers/kwallet.cpp
if (WITH_KWALLET)
target_sources(squawk PRIVATE
kwallet.cpp
kwallet.h
)
add_library(kwalletWrapper SHARED ${kwalletW_SRC})
target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
target_link_libraries(kwalletWrapper KF5::Wallet)
target_link_libraries(kwalletWrapper Qt5::Core)
install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()
add_subdirectory(wrappers)
target_include_directories(squawk PRIVATE $<TARGET_PROPERTY:KF5::Wallet,INTERFACE_INCLUDE_DIRECTORIES>)
endif ()

View File

@ -0,0 +1,2 @@
add_library(kwalletWrapper SHARED kwallet.cpp)
target_link_libraries(kwalletWrapper PRIVATE KF5::Wallet)

View File

@ -122,7 +122,22 @@ void Core::RosterItem::nextRequest()
{
if (syncronizing) {
if (requestedCount != -1) {
emit historyResponse(responseCache);
bool last = false;
if (archiveState == beginning || archiveState == complete) {
QString firstId = archive->oldestId();
if (responseCache.size() == 0) {
if (requestedBefore == firstId) {
last = true;
}
} else {
if (responseCache.front().getId() == firstId) {
last = true;
}
}
} else if (archiveState == empty && responseCache.size() == 0) {
last = true;
}
emit historyResponse(responseCache, last);
}
}
if (requestCache.size() > 0) {
@ -360,6 +375,11 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
archiveState = complete;
archive->setFromTheBeginning(true);
}
if (added == 0 && wasEmpty) {
archiveState = empty;
nextRequest();
break;
}
if (requestedCount != -1) {
QString before;
if (responseCache.size() > 0) {
@ -378,7 +398,7 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
} catch (const Archive::Empty& e) {
}
if (!found || requestedCount > responseCache.size()) {
if (!found || requestedCount > int(responseCache.size())) {
if (archiveState == complete) {
nextRequest();
} else {
@ -529,7 +549,7 @@ void Core::RosterItem::clearArchiveRequests()
requestedBefore = "";
for (const std::pair<int, QString>& pair : requestCache) {
if (pair.first != -1) {
emit historyResponse(responseCache); //just to notify those who still waits with whatever happened to be left in caches yet
emit historyResponse(responseCache, false); //just to notify those who still waits with whatever happened to be left in caches yet
}
responseCache.clear();
}
@ -549,3 +569,20 @@ void Core::RosterItem::downgradeDatabaseState()
archiveState = ArchiveState::chunk;
}
}
Shared::Message Core::RosterItem::getMessage(const QString& id)
{
for (const Shared::Message& msg : appendCache) {
if (msg.getId() == id) {
return msg;
}
}
for (Shared::Message& msg : hisoryCache) {
if (msg.getId() == id) {
return msg;
}
}
return archive->getElement(id);
}

View File

@ -78,10 +78,12 @@ public:
void clearArchiveRequests();
void downgradeDatabaseState();
Shared::Message getMessage(const QString& id);
signals:
void nameChanged(const QString& name);
void subscriptionStateChanged(Shared::SubscriptionState state);
void historyResponse(const std::list<Shared::Message>& messages);
void historyResponse(const std::list<Shared::Message>& messages, bool last);
void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime());
void avatarChanged(Shared::Avatar, const QString& path);
void requestVCard(const QString& jid);

View File

@ -32,11 +32,10 @@ Core::Squawk::Squawk(QObject* parent):
,kwallet()
#endif
{
connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse);
connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress);
connect(&network, &NetworkAccess::downloadFileError, this, &Squawk::downloadFileError);
connect(&network, &NetworkAccess::uploadFileProgress, this, &Squawk::uploadFileProgress);
connect(&network, &NetworkAccess::uploadFileError, this, &Squawk::uploadFileError);
connect(&network, &NetworkAccess::loadFileProgress, this, &Squawk::fileProgress);
connect(&network, &NetworkAccess::loadFileError, this, &Squawk::fileError);
connect(&network, &NetworkAccess::downloadFileComplete, this, &Squawk::fileDownloadComplete);
connect(&network, &NetworkAccess::uploadFileComplete, this, &Squawk::fileUploadComplete);
#ifdef WITH_KWALLET
if (kwallet.supportState() == PSE::KWallet::success) {
@ -168,7 +167,7 @@ void Core::Squawk::addAccount(
connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard);
connect(acc, &Account::uploadFileError, this, &Squawk::uploadFileError);
connect(acc, &Account::uploadFileError, this, &Squawk::onAccountUploadFileError);
QMap<QString, QVariant> map = {
{"login", login},
@ -336,17 +335,6 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da
itr->second->sendMessage(data);
}
void Core::Squawk::sendMessage(const QString& account, const Shared::Message& data, const QString& path)
{
AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) {
qDebug("An attempt to send a message with non existing account, skipping");
return;
}
itr->second->sendMessage(data, path);
}
void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before)
{
AccountsMap::const_iterator itr = amap.find(account);
@ -357,10 +345,10 @@ void Core::Squawk::requestArchive(const QString& account, const QString& jid, in
itr->second->requestArchive(jid, count, before);
}
void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list)
void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last)
{
Account* acc = static_cast<Account*>(sender());
emit responseArchive(acc->getName(), jid, list);
emit responseArchive(acc->getName(), jid, list, last);
}
void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map)
@ -604,14 +592,9 @@ void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, co
itr->second->addRoomRequest(jid, nick, password, autoJoin);
}
void Core::Squawk::fileLocalPathRequest(const QString& messageId, const QString& url)
void Core::Squawk::fileDownloadRequest(const QString& url)
{
network.fileLocalPathRequest(messageId, url);
}
void Core::Squawk::downloadFileRequest(const QString& messageId, const QString& url)
{
network.downladFileRequest(messageId, url);
network.downladFile(url);
}
void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName)
@ -688,7 +671,7 @@ void Core::Squawk::readSettings()
settings.value("login").toString(),
settings.value("server").toString(),
settings.value("password", "").toString(),
settings.value("name").toString(),
settings.value("name").toString(),
settings.value("resource").toString(),
Shared::Global::fromInt<Shared::AccountPassword>(settings.value("passwordType", static_cast<int>(Shared::AccountPassword::plain)).toInt())
);
@ -762,3 +745,26 @@ void Core::Squawk::onWalletResponsePassword(const QString& login, const QString&
emit changeAccount(login, {{"password", password}});
accountReady();
}
void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText)
{
Account* acc = static_cast<Account*>(sender());
emit fileError({{acc->getName(), jid, id}}, errorText, true);
}
void Core::Squawk::onLocalPathInvalid(const QString& path)
{
std::list<Shared::MessageInfo> list = network.reportPathInvalid(path);
QMap<QString, QVariant> data({
{"attachPath", ""}
});
for (const Shared::MessageInfo& info : list) {
AccountsMap::const_iterator itr = amap.find(info.account);
if (itr != amap.end()) {
itr->second->requestChangeMessage(info.jid, info.messageId, data);
} else {
qDebug() << "Reacting on failure to reach file" << path << "there was an attempt to change message in account" << info.account << "which doesn't exist, skipping";
}
}
}

View File

@ -51,31 +51,39 @@ public:
signals:
void quit();
void ready();
void newAccount(const QMap<QString, QVariant>&);
void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
void removeAccount(const QString& account);
void addGroup(const QString& account, const QString& name);
void removeGroup(const QString& account, const QString& name);
void addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data);
void removeContact(const QString& account, const QString& jid);
void removeContact(const QString& account, const QString& jid, const QString& group);
void changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data);
void addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
void removePresence(const QString& account, const QString& jid, const QString& name);
void stateChanged(Shared::Availability state);
void accountMessage(const QString& account, const Shared::Message& data);
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list);
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
void removeRoom(const QString& account, const QString jid);
void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
void fileLocalPathResponse(const QString& messageId, const QString& path);
void downloadFileError(const QString& messageId, const QString& error);
void downloadFileProgress(const QString& messageId, qreal value);
void uploadFileError(const QString& messageId, const QString& error);
void uploadFileProgress(const QString& messageId, qreal value);
void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
void responseVCard(const QString& jid, const Shared::VCard& card);
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
void requestPassword(const QString& account);
@ -83,15 +91,18 @@ signals:
public slots:
void start();
void stop();
void newAccountRequest(const QMap<QString, QVariant>& map);
void modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map);
void removeAccountRequest(const QString& name);
void connectAccount(const QString& account);
void disconnectAccount(const QString& account);
void changeState(Shared::Availability state);
void sendMessage(const QString& account, const Shared::Message& data);
void sendMessage(const QString& account, const Shared::Message& data, const QString& path);
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName);
@ -99,15 +110,18 @@ public slots:
void removeContactRequest(const QString& account, const QString& jid);
void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
void setRoomJoined(const QString& account, const QString& jid, bool joined);
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
void removeRoomRequest(const QString& account, const QString& jid);
void fileLocalPathRequest(const QString& messageId, const QString& url);
void downloadFileRequest(const QString& messageId, const QString& url);
void fileDownloadRequest(const QString& url);
void requestVCard(const QString& account, const QString& jid);
void uploadVCard(const QString& account, const Shared::VCard& card);
void responsePassword(const QString& account, const QString& password);
void onLocalPathInvalid(const QString& path);
private:
typedef std::deque<Account*> Accounts;
@ -146,7 +160,7 @@ private slots:
void onAccountAddPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
void onAccountRemovePresence(const QString& jid, const QString& name);
void onAccountMessage(const Shared::Message& data);
void onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list);
void onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last);
void onAccountAddRoom(const QString jid, const QMap<QString, QVariant>& data);
void onAccountChangeRoom(const QString jid, const QMap<QString, QVariant>& data);
void onAccountRemoveRoom(const QString jid);
@ -155,6 +169,8 @@ private slots:
void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
void onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText);
void onWalletOpened(bool success);
void onWalletResponsePassword(const QString& login, const QString& password);
void onWalletRejectPassword(const QString& login);

491
core/urlstorage.cpp Normal file
View File

@ -0,0 +1,491 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QStandardPaths>
#include <QDir>
#include <QDebug>
#include "urlstorage.h"
Core::UrlStorage::UrlStorage(const QString& p_name):
name(p_name),
opened(false),
environment(),
base(),
map()
{
}
Core::UrlStorage::~UrlStorage()
{
close();
}
void Core::UrlStorage::open()
{
if (!opened) {
mdb_env_create(&environment);
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
path += "/" + name;
QDir cache(path);
if (!cache.exists()) {
bool res = cache.mkpath(path);
if (!res) {
throw Archive::Directory(path.toStdString());
}
}
mdb_env_set_maxdbs(environment, 2);
mdb_env_set_mapsize(environment, 10UL * 1024UL * 1024UL);
mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
mdb_dbi_open(txn, "base", MDB_CREATE, &base);
mdb_dbi_open(txn, "map", MDB_CREATE, &map);
mdb_txn_commit(txn);
opened = true;
}
}
void Core::UrlStorage::close()
{
if (opened) {
mdb_dbi_close(environment, map);
mdb_dbi_close(environment, base);
mdb_env_close(environment);
opened = false;
}
}
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, bool overwrite)
{
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
try {
writeInfo(key, info, txn, overwrite);
mdb_txn_commit(txn);
} catch (...) {
mdb_txn_abort(txn);
throw;
}
}
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, MDB_txn* txn, bool overwrite)
{
QByteArray ba;
QDataStream ds(&ba, QIODevice::WriteOnly);
info.serialize(ds);
const std::string& id = key.toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
lmdbData.mv_size = ba.size();
lmdbData.mv_data = (uint8_t*)ba.data();
int rc;
rc = mdb_put(txn, base, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE);
if (rc != 0) {
if (rc == MDB_KEYEXIST) {
if (!overwrite) {
throw Archive::Exist(name.toStdString(), id);
}
} else {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
}
if (info.hasPath()) {
std::string sp = info.getPath().toStdString();
lmdbData.mv_size = sp.size();
lmdbData.mv_data = (char*)sp.c_str();
rc = mdb_put(txn, map, &lmdbData, &lmdbKey, 0);
if (rc != 0) {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
}
}
void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info, MDB_txn* txn)
{
const std::string& id = key.toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
int rc = mdb_get(txn, base, &lmdbKey, &lmdbData);
if (rc == 0) {
QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
QDataStream ds(&ba, QIODevice::ReadOnly);
info.deserialize(ds);
} else if (rc == MDB_NOTFOUND) {
throw Archive::NotFound(id, name.toStdString());
} else {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
}
void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info)
{
MDB_txn *txn;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
try {
readInfo(key, info, txn);
mdb_txn_commit(txn);
} catch (...) {
mdb_txn_abort(txn);
throw;
}
}
void Core::UrlStorage::addFile(const QString& url)
{
if (!opened) {
throw Archive::Closed("addFile(no message, no path)", name.toStdString());
}
addToInfo(url, "", "", "");
}
void Core::UrlStorage::addFile(const QString& url, const QString& path)
{
if (!opened) {
throw Archive::Closed("addFile(no message, with path)", name.toStdString());
}
addToInfo(url, "", "", "", path);
}
void Core::UrlStorage::addFile(const QString& url, const QString& account, const QString& jid, const QString& id)
{
if (!opened) {
throw Archive::Closed("addFile(with message, no path)", name.toStdString());
}
addToInfo(url, account, jid, id);
}
void Core::UrlStorage::addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
{
if (!opened) {
throw Archive::Closed("addFile(with message, with path)", name.toStdString());
}
addToInfo(url, account, jid, id, path);
}
void Core::UrlStorage::addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path)
{
if (!opened) {
throw Archive::Closed("addFile(with list)", name.toStdString());
}
UrlInfo info (path, msgs);
writeInfo(url, info, true);;
}
QString Core::UrlStorage::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
{
if (!opened) {
throw Archive::Closed("addMessageAndCheckForPath", name.toStdString());
}
return addToInfo(url, account, jid, id).getPath();
}
Core::UrlStorage::UrlInfo Core::UrlStorage::addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path)
{
UrlInfo info;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
try {
readInfo(url, info, txn);
} catch (const Archive::NotFound& e) {
} catch (...) {
mdb_txn_abort(txn);
throw;
}
bool pathChange = false;
bool listChange = false;
if (path != "-s") {
if (info.getPath() != path) {
info.setPath(path);
pathChange = true;
}
}
if (account.size() > 0 && jid.size() > 0 && id.size() > 0) {
listChange = info.addMessage(account, jid, id);
}
if (pathChange || listChange) {
try {
writeInfo(url, info, txn, true);
mdb_txn_commit(txn);
} catch (...) {
mdb_txn_abort(txn);
throw;
}
} else {
mdb_txn_abort(txn);
}
return info;
}
std::list<Shared::MessageInfo> Core::UrlStorage::setPath(const QString& url, const QString& path)
{
std::list<Shared::MessageInfo> list;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
UrlInfo info;
try {
readInfo(url, info, txn);
info.getMessages(list);
} catch (const Archive::NotFound& e) {
} catch (...) {
mdb_txn_abort(txn);
throw;
}
info.setPath(path);
try {
writeInfo(url, info, txn, true);
mdb_txn_commit(txn);
} catch (...) {
mdb_txn_abort(txn);
throw;
}
return list;
}
std::list<Shared::MessageInfo> Core::UrlStorage::removeFile(const QString& url)
{
std::list<Shared::MessageInfo> list;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
UrlInfo info;
try {
std::string id = url.toStdString();
readInfo(url, info, txn);
info.getMessages(list);
MDB_val lmdbKey;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
int rc = mdb_del(txn, base, &lmdbKey, NULL);
if (rc != 0) {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
if (info.hasPath()) {
std::string path = info.getPath().toStdString();
lmdbKey.mv_size = path.size();
lmdbKey.mv_data = (char*)path.c_str();
int rc = mdb_del(txn, map, &lmdbKey, NULL);
if (rc != 0) {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
}
mdb_txn_commit(txn);
} catch (...) {
mdb_txn_abort(txn);
throw;
}
return list;
}
std::list<Shared::MessageInfo> Core::UrlStorage::deletedFile(const QString& path)
{
std::list<Shared::MessageInfo> list;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
try {
std::string spath = path.toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = spath.size();
lmdbKey.mv_data = (char*)spath.c_str();
QString url;
int rc = mdb_get(txn, map, &lmdbKey, &lmdbData);
if (rc == 0) {
std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size);
url = QString(surl.c_str());
} else if (rc == MDB_NOTFOUND) {
qDebug() << "Have been asked to remove file" << path << ", which isn't in the database, skipping";
mdb_txn_abort(txn);
return list;
} else {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
UrlInfo info;
std::string id = url.toStdString();
readInfo(url, info, txn);
info.getMessages(list);
info.setPath(QString());
writeInfo(url, info, txn, true);
rc = mdb_del(txn, map, &lmdbKey, NULL);
if (rc != 0) {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
mdb_txn_commit(txn);
} catch (...) {
mdb_txn_abort(txn);
throw;
}
return list;
}
QString Core::UrlStorage::getUrl(const QString& path)
{
std::list<Shared::MessageInfo> list;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
std::string spath = path.toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = spath.size();
lmdbKey.mv_data = (char*)spath.c_str();
QString url;
int rc = mdb_get(txn, map, &lmdbKey, &lmdbData);
if (rc == 0) {
std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size);
url = QString(surl.c_str());
mdb_txn_abort(txn);
return url;
} else if (rc == MDB_NOTFOUND) {
mdb_txn_abort(txn);
throw Archive::NotFound(spath, name.toStdString());
} else {
mdb_txn_abort(txn);
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
}
std::pair<QString, std::list<Shared::MessageInfo>> Core::UrlStorage::getPath(const QString& url)
{
UrlInfo info;
readInfo(url, info);
std::list<Shared::MessageInfo> container;
info.getMessages(container);
return std::make_pair(info.getPath(), container);
}
Core::UrlStorage::UrlInfo::UrlInfo():
localPath(),
messages() {}
Core::UrlStorage::UrlInfo::UrlInfo(const QString& path):
localPath(path),
messages() {}
Core::UrlStorage::UrlInfo::UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs):
localPath(path),
messages(msgs) {}
Core::UrlStorage::UrlInfo::~UrlInfo() {}
bool Core::UrlStorage::UrlInfo::addMessage(const QString& acc, const QString& jid, const QString& id)
{
for (const Shared::MessageInfo& info : messages) {
if (info.account == acc && info.jid == jid && info.messageId == id) {
return false;
}
}
messages.emplace_back(acc, jid, id);
return true;
}
void Core::UrlStorage::UrlInfo::serialize(QDataStream& data) const
{
data << localPath;
std::list<Shared::MessageInfo>::size_type size = messages.size();
data << quint32(size);
for (const Shared::MessageInfo& info : messages) {
data << info.account;
data << info.jid;
data << info.messageId;
}
}
void Core::UrlStorage::UrlInfo::deserialize(QDataStream& data)
{
data >> localPath;
quint32 size;
data >> size;
for (quint32 i = 0; i < size; ++i) {
messages.emplace_back();
Shared::MessageInfo& info = messages.back();
data >> info.account;
data >> info.jid;
data >> info.messageId;
}
}
void Core::UrlStorage::UrlInfo::getMessages(std::list<Shared::MessageInfo>& container) const
{
for (const Shared::MessageInfo& info : messages) {
container.emplace_back(info);
}
}
QString Core::UrlStorage::UrlInfo::getPath() const
{
return localPath;
}
bool Core::UrlStorage::UrlInfo::hasPath() const
{
return localPath.size() > 0;
}
void Core::UrlStorage::UrlInfo::setPath(const QString& path)
{
localPath = path;
}

99
core/urlstorage.h Normal file
View File

@ -0,0 +1,99 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CORE_URLSTORAGE_H
#define CORE_URLSTORAGE_H
#include <QString>
#include <QDataStream>
#include <lmdb.h>
#include <list>
#include "archive.h"
#include <shared/messageinfo.h>
namespace Core {
/**
* @todo write docs
*/
class UrlStorage
{
class UrlInfo;
public:
UrlStorage(const QString& name);
~UrlStorage();
void open();
void close();
void addFile(const QString& url);
void addFile(const QString& url, const QString& path);
void addFile(const QString& url, const QString& account, const QString& jid, const QString& id);
void addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
void addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path); //this one overwrites all that was
std::list<Shared::MessageInfo> removeFile(const QString& url); //removes entry like it never was in the database, returns affected message infos
std::list<Shared::MessageInfo> deletedFile(const QString& path); //empties the localPath of the entry, returns affected message infos
std::list<Shared::MessageInfo> setPath(const QString& url, const QString& path);
QString getUrl(const QString& path);
QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
std::pair<QString, std::list<Shared::MessageInfo>> getPath(const QString& url);
private:
QString name;
bool opened;
MDB_env* environment;
MDB_dbi base;
MDB_dbi map;
private:
void writeInfo(const QString& key, const UrlInfo& info, bool overwrite = false);
void writeInfo(const QString& key, const UrlInfo& info, MDB_txn* txn, bool overwrite = false);
void readInfo(const QString& key, UrlInfo& info);
void readInfo(const QString& key, UrlInfo& info, MDB_txn* txn);
UrlInfo addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path = "-s");
private:
class UrlInfo {
public:
UrlInfo(const QString& path);
UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs);
UrlInfo();
~UrlInfo();
void serialize(QDataStream& data) const;
void deserialize(QDataStream& data);
QString getPath() const;
bool hasPath() const;
void setPath(const QString& path);
bool addMessage(const QString& acc, const QString& jid, const QString& id);
void getMessages(std::list<Shared::MessageInfo>& container) const;
private:
QString localPath;
std::list<Shared::MessageInfo> messages;
};
};
}
#endif // CORE_URLSTORAGE_H

1
external/signal-protocol-c vendored Submodule

@ -0,0 +1 @@
Subproject commit 3a83a4f4ed2302ff6e68ab569c88793b50c22d28

View File

@ -1,16 +1,10 @@
cmake_minimum_required(VERSION 3.0)
project(simplecrypt)
project(simplecrypt LANGUAGES CXX)
set(CMAKE_AUTOMOC ON)
find_package(Qt5Core CONFIG REQUIRED)
find_package(Qt5 COMPONENTS Core REQUIRED)
set(simplecrypt_SRC
simplecrypt.cpp
)
add_library(simpleCrypt STATIC simplecrypt.cpp simplecrypt.h)
# Tell CMake to create the helloworld executable
add_library(simpleCrypt ${simplecrypt_SRC})
# Use the Widgets module from Qt 5.
target_link_libraries(simpleCrypt Qt5::Core)

3
packaging/CMakeLists.txt Normal file
View File

@ -0,0 +1,3 @@
configure_file(squawk.desktop squawk.desktop COPYONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)

4
plugins/CMakeLists.txt Normal file
View File

@ -0,0 +1,4 @@
if (WITH_KIO)
add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp)
target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets)
endif ()

View File

@ -0,0 +1,8 @@
#include <QUrl>
#include <QObject>
#include <KIO/OpenFileManagerWindowJob>
extern "C" void highlightInFileManager(const QUrl& url) {
KIO::OpenFileManagerWindowJob* job = KIO::highlightInFileManager({url});
QObject::connect(job, &KIO::OpenFileManagerWindowJob::result, job, &KIO::OpenFileManagerWindowJob::deleteLater);
}

25
qomemo/CMakeLists.txt Normal file
View File

@ -0,0 +1,25 @@
target_sources(squawk PRIVATE
bundle.cpp
bundle.h
database.cpp
database.h
device.cpp
device.h
device_key_storage.cpp
device_key_storage.h
device_service.cpp
device_service.h
key.cpp
key.h
qomemo.cpp
qomemo.h
sce.cpp
sce.h
user_device_list.cpp
user_device_list.h
qxmpp_omemo_manager.cpp
qxmpp_omemo_manager.h
)
add_subdirectory(signal)
add_subdirectory(variant)

5
qomemo/bundle.cpp Normal file
View File

@ -0,0 +1,5 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "bundle.h"

34
qomemo/bundle.h Normal file
View File

@ -0,0 +1,34 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QList>
#include <QString>
#include <QBuffer>
class QXmppPubSubIq;
class QXmppElement;
class QXmppIq;
namespace QXmpp::Omemo {
class PreKey {
public:
int id;
QByteArray data;
};
class Bundle {
public:
QByteArray spk;
int spkId;
QByteArray spks;
QByteArray ik;
QList<PreKey> prekeys;
};
} // namespace QXmpp::Omemo

180
qomemo/database.cpp Normal file
View File

@ -0,0 +1,180 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "database.h"
#include "bundle.h"
#include <QDebug>
#include <QDir>
#include <QException>
#include <QStandardPaths>
#include <QString>
using namespace QXmpp::Omemo;
Database::Database(QString jid) : jid(std::move(jid)) {
auto cacheLocation =
QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
auto path = QString("%1/.omemo/%2").arg(cacheLocation, jid);
QDir cache(path);
if (!cache.exists() && !cache.mkpath(path)) {
qWarning() << "Could not create:" << path;
throw QException();
}
mdb_env_create(&env);
mdb_env_set_maxdbs(env, 5);
mdb_env_set_mapsize(env, 512UL * 1024UL * 1024UL);
mdb_env_open(env, path.toStdString().c_str(), 0, 0664);
MDB_txn *txn;
mdb_txn_begin(env, nullptr, 0, &txn);
mdb_dbi_open(txn, "keys", MDB_CREATE, &dbiKeys);
mdb_dbi_open(txn, "devices", MDB_CREATE, &dbiDevices);
mdb_dbi_open(txn, "identity_keys", MDB_CREATE, &dbiIdentityKeys);
mdb_txn_commit(txn);
}
Database::~Database() {
mdb_dbi_close(env, dbiKeys);
mdb_dbi_close(env, dbiDevices);
mdb_dbi_close(env, dbiIdentityKeys);
mdb_env_close(env);
}
std::optional<QByteArray> Database::loadIdentityKeySecret(int deviceId) { return std::nullopt; }
bool Database::saveIdentityKeySecret(int deviceId,
const QByteArray &identityKeySecret) {
MDB_val mdbKey, mdbValue;
auto key = QString("%1/secret").arg(QString::number(deviceId)).toStdString();
mdbKey.mv_data = key.data();
mdbKey.mv_size = key.size();
mdbValue.mv_data = const_cast<char *>(identityKeySecret.data());
mdbValue.mv_size = identityKeySecret.size();
MDB_txn *txn;
mdb_txn_begin(env, nullptr, 0, &txn);
auto err = mdb_put(txn, dbiIdentityKeys, &mdbKey, &mdbValue, MDB_NOOVERWRITE);
if (!err) {
mdb_txn_commit(txn);
return true;
}
qWarning() << "could not save identity key secret:" << mdb_strerror(err);
mdb_txn_abort(txn);
return false;
}
std::optional<int> Database::loadActiveDeviceId() {
MDB_val key, value;
key.mv_data = (void *) "active";
key.mv_size = sizeof("active");
MDB_txn *txn;
mdb_txn_begin(env, nullptr, 0, &txn);
auto err = mdb_get(txn, dbiIdentityKeys, &key, &value);
if (err) {
qWarning() << "could not load active device id:" << mdb_strerror(err);
mdb_txn_abort(txn);
return std::nullopt;
}
if (value.mv_size != sizeof(int)) {
qWarning() << "mv_size is" << value.mv_size << "instead of" << sizeof(int);
mdb_txn_abort(txn);
return std::nullopt;
}
auto id = *reinterpret_cast<int *>(value.mv_data);
mdb_txn_abort(txn);
return id;
}
bool Database::saveActiveDeviceId(int deviceId) {
MDB_val key, value;
key.mv_data = (void *) "active";
key.mv_size = sizeof("active");
value.mv_data = &deviceId;
value.mv_size = sizeof(deviceId);
MDB_txn *txn;
mdb_txn_begin(env, nullptr, 0, &txn);
auto err = mdb_put(txn, dbiIdentityKeys, &key, &value, 0);
if (err) {
qWarning() << "could not save active device id" << mdb_strerror(err);
return false;
}
err = mdb_txn_commit(txn);
if (err) {
qWarning() << "could not save active device id" << mdb_strerror(err);
return false;
}
return true;
}
bool Database::saveIdentityKey(int deviceId, const QByteArray &identityKey) {
return false;
}
std::optional<QByteArray> Database::loadIdentityKey(int deviceId) {
return std::nullopt;
}
bool Database::containsPreKey() {
return false;
}
std::optional<KeyPair> Database::loadPreKey(int deviceId, int id) {
return std::nullopt;
}
bool Database::savePreKey(int deviceId, int id, const KeyPair &preKey) {
return false;
}
std::optional<Bundle> Database::loadBundle(int deviceId) {
Bundle result{};
auto ik = loadIdentityKey(deviceId);
result.ik = ik.value();
auto spk = loadSignedPreKey(deviceId);
result.spk = spk->key.publicKey;
result.spks = spk->signature;
result.spkId = spk->id;
// PreKeys
return result;
}
bool Database::saveBundle(int deviceId, const Bundle &bundle) {
return false;
}
std::optional<SignedPreKey> Database::loadSignedPreKey(int deviceId) {
return std::optional<SignedPreKey>();
}
bool Database::saveSignedPreKey(int deviceId, const SignedPreKey &signedPreKey) {
return false;
}

56
qomemo/database.h Normal file
View File

@ -0,0 +1,56 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <QBuffer>
#include <QString>
#include <lmdb.h>
#include "key.h"
namespace QXmpp::Omemo {
class Bundle;
class Database {
public:
explicit Database(QString jid);
~Database();
Database(const Database &) = delete;
Database(Database &&) = delete;
Database &operator=(const Database &) = delete;
// For local user
std::optional<int> loadActiveDeviceId();
bool saveActiveDeviceId(int deviceId);
std::optional<QByteArray> loadIdentityKeySecret(int deviceId);
bool saveIdentityKeySecret(int deviceId, const QByteArray &identityKeySecret);
std::optional<Bundle> loadBundle(int deviceId);
bool saveBundle(int deviceId, const Bundle& bundle);
// For any user
std::optional<QByteArray> loadIdentityKey(int deviceId);
bool saveIdentityKey(int deviceId, const QByteArray &identityKey);
bool containsPreKey();
std::optional<KeyPair> loadPreKey(int deviceId, int id);
bool savePreKey(int deviceId, int id, const KeyPair &preKey);
std::optional<SignedPreKey> loadSignedPreKey(int deviceId);
bool saveSignedPreKey(int deviceId, const SignedPreKey& signedPreKey);
const QString jid;
private:
MDB_env *env{};
MDB_dbi dbiDevices{};
MDB_dbi dbiKeys{};
MDB_dbi dbiPreKeys{};
MDB_dbi dbiIdentityKeys{};
};
} // namespace QXmpp::Omemo

5
qomemo/device.cpp Normal file
View File

@ -0,0 +1,5 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "device.h"

25
qomemo/device.h Normal file
View File

@ -0,0 +1,25 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <QList>
class QXmppElement;
class QXmppIq;
namespace QXmpp::Omemo {
class Device {
public:
int id;
};
class DeviceList {
public:
QList<Device> devices;
};
} // namespace QXmpp::Omemo

View File

@ -0,0 +1,14 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "device_key_storage.h"
#include <QRandomGenerator>
int QXmpp::Omemo::DeviceKeyStorage::generateDeviceId() {
QRandomGenerator random{};
return 1 + random.bounded(INT32_MAX - 1);
}
QXmpp::Omemo::DeviceKeyStorage::DeviceKeyStorage(int deviceId) : deviceId(deviceId) {}

View File

@ -0,0 +1,18 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
namespace QXmpp::Omemo {
class DeviceKeyStorage {
public:
static int generateDeviceId();
explicit DeviceKeyStorage(int deviceId);
const int deviceId;
};
} // namespace QXmpp::Omemo

35
qomemo/device_service.cpp Normal file
View File

@ -0,0 +1,35 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "device_service.h"
#include "device.h"
using namespace QXmpp::Omemo;
DeviceService::DeviceService(QObject *parent) : QObject(parent) {}
void DeviceService::onDeviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list) {
for (const auto &device : list.devices) {
qInfo() << "Got device for" << jid << ":" << device.id;
}
}
QSharedPointer<Database> DeviceService::getDatabase(const QString &jid) {
if (!databases.contains(jid)) {
databases.insert(jid, QSharedPointer<Database>::create(jid));
}
return databases[jid];
}
void DeviceService::addIdentity(const QString &jid, int deviceId, const QByteArray& publicKey) {
auto db = getDatabase(jid);
db->saveIdentityKey(deviceId, publicKey);
}
void DeviceService::onDeviceListNotFound(const QString &jid) {
qInfo() << "Device list not found:" << jid;
}

38
qomemo/device_service.h Normal file
View File

@ -0,0 +1,38 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QBuffer>
#include "qomemo.h"
#include "user_device_list.h"
#include "database.h"
#include <QXmppClient.h>
namespace QXmpp::Omemo {
class DeviceList;
class DeviceService : public QObject {
Q_OBJECT
public:
explicit DeviceService(QObject *parent);
QSharedPointer<Database> getDatabase(const QString& jid);
public slots:
void addIdentity(const QString& jid, int deviceId, const QByteArray& publicKey);
void onDeviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list);
void onDeviceListNotFound(const QString &jid);
private:
QMap<QString, QSharedPointer<Database>> databases{};
};
} // namespace QXmpp::Omemo

6
qomemo/key.cpp Normal file
View File

@ -0,0 +1,6 @@
/*
* Created by victoria on 2021-05-15.
*/
#include "key.h"

27
qomemo/key.h Normal file
View File

@ -0,0 +1,27 @@
/*
* Created by victoria on 2021-05-15.
*/
#pragma once
#include <optional>
#include <QByteArray>
namespace QXmpp::Omemo {
class KeyPair {
public:
QByteArray publicKey{};
std::optional<QByteArray> secretKey{ std::nullopt };
};
class SignedPreKey {
public:
int id{ 0 };
KeyPair key{};
QByteArray signature{};
};
}

94
qomemo/qomemo.cpp Normal file
View File

@ -0,0 +1,94 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "qomemo.h"
#include "sce.h"
#include <shared/qxmppfactories.h>
#include <QBuffer>
#include <QDebug>
#include <QDomDocument>
using namespace QXmpp::Factories;
QXmppElement QXmpp::Omemo::EncryptedMessage::header() const {
auto result = createElement("header");
result.setAttribute("sid", QString::number(fromDeviceId));
auto ivNode = createElement("iv");
ivNode.setValue(iv);
for (const auto &key : keys) {
result.appendChild(key.toXml());
}
return result;
}
QXmppElement QXmpp::Omemo::EncryptedMessage::toXml() const {
auto result = createElement("encrypted", "eu.siacs.conversations.axolotl");
result.appendChild(header());
// TODO: Payload is optional
result.appendChild(payload());
return result;
}
QXmppElement QXmpp::Omemo::EncryptedMessage::payload() const {
QBuffer buffer;
buffer.open(QIODevice::ReadWrite);
QXmlStreamWriter writer(&buffer);
message.toXml(&writer);
QDomDocument doc;
doc.setContent(buffer.data(), true);
QXmppElement root(doc.documentElement());
root.setTagName("payload");
return root;
}
QXmppElement QXmpp::Omemo::EncryptedMessage::content() const {
auto envelope = createElement("content", "urn:xmpp:sce:0");
envelope.appendChild(payload());
if (!from.isEmpty()) {
auto fromNode = createElement("from");
fromNode.setAttribute("jid", from);
envelope.appendChild(fromNode);
}
if (!to.isEmpty()) {
auto toNode = createElement("to");
toNode.setAttribute("jid", to);
envelope.appendChild(toNode);
}
if (!timestamp.isNull()) {
auto timeNode = createElement("time");
timeNode.setAttribute("stamp", timestamp.toString(Qt::DateFormat::ISODate));
envelope.appendChild(timeNode);
}
auto rpad = createElement("rpad");
rpad.setValue(QXmpp::Sce::generatePadding());
envelope.appendChild(rpad);
return envelope;
}
QXmppElement QXmpp::Omemo::MessageKey::toXml() const {
auto result = createElement("key");
result.setAttribute("rid", QString::number(receivingDeviceId));
if (prekey)
result.setAttribute("prekey", "true");
result.setValue(key);
return result;
}

42
qomemo/qomemo.h Normal file
View File

@ -0,0 +1,42 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QXmppPubSubIq.h>
#include <QDateTime>
#include <QXmppMessage.h>
namespace QXmpp::Omemo {
class MessageKey {
public:
[[nodiscard]] QXmppElement toXml() const;
int receivingDeviceId{};
bool prekey{};
QString key{};
};
class EncryptedMessage {
public:
[[nodiscard]] QXmppElement header() const;
[[nodiscard]] QXmppElement content() const;
[[nodiscard]] QXmppElement toXml() const;
[[nodiscard]] QXmppElement payload() const;
int fromDeviceId{};
QList<MessageKey> keys{};
QString from{};
QString to{};
QDateTime timestamp{};
QString iv{};
QXmppMessage message{};
};
} // namespace QXmpp::Omemo

View File

@ -0,0 +1,292 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "qxmpp_omemo_manager.h"
#include "qomemo/signal/context.h"
#include "bundle.h"
#include "device.h"
#include "variant/conversations.h"
#include <QDomElement>
#include <QXmppClient.h>
#include <QXmppPubSubIq.h>
#include <iostream>
#include <QRandomGenerator64>
#include <external/signal-protocol-c/src/signal_protocol_internal.h>
using namespace QXmpp::Omemo;
Manager::Manager()
: deviceService{new DeviceService(this)},
omemoVariant(new Variant::Conversations),
signalContext(new Signal::Context) {
connect(this, &Manager::deviceListReceived, deviceService.get(), &DeviceService::onDeviceListReceived);
connect(this, &Manager::deviceListNotFound, deviceService.get(), &DeviceService::onDeviceListNotFound);
connect(this, &Manager::deviceListNotFound, this, [this](const QString &jid) {
if (jid == client()->configuration().jidBare())
generateDeviceListForSelf();
});
connect(this, &Manager::deviceListReceived, this, [this](const QString &jid, const DeviceList &currentList) {
if (jid == client()->configuration().jidBare())
generateDeviceForSelfIfNeeded(currentList);
});
}
bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) {
QString str{};
QTextStream info(&str);
stanza.save(info, 4);
std::cout << str.toStdString();
if (handleDeviceList(stanza) || handleMissingDeviceList(stanza) || handleEncryptedMessage(stanza))
return true;
return false;
}
bool Manager::handleDeviceList(const QDomElement &stanza) {
if (!(stanza.tagName() == "iq"))
return false;
if (!(stanza.attribute("type") == "result"))
return false;
auto pubsub = stanza.firstChildElement("pubsub");
if (pubsub.isNull())
return false;
auto items = pubsub.firstChildElement("items");
if (!(items.attribute("node") == omemoVariant->getDeviceListNode()))
return false;
auto deviceList = omemoVariant->latestDeviceListFromPubSubNode(items);
if (!deviceList.has_value())
return false;
emit deviceListReceived(stanza.attribute("from"), deviceList.value());
return true;
}
bool Manager::handleMissingDeviceList(const QDomElement &stanza) {
if (stanza.tagName() != "iq")
return false;
if (stanza.attribute("type") != "error")
return false;
auto pubsub = stanza.firstChildElement("pubsub");
if (pubsub.isNull())
return false;
auto items = pubsub.firstChildElement("items");
if (items.attribute("node") != omemoVariant->getDeviceListNode())
return false;
auto error = stanza.firstChildElement("error");
if (error.namespaceURI() != "jabber:client" || error.attribute("code") != "404")
return false;
qDebug() << "Got 404 deviceList for" << stanza.attribute("from");
emit deviceListNotFound(stanza.attribute("from"));
return true;
}
bool Manager::handleEncryptedMessage(const QDomElement &stanza) {
if (stanza.tagName() != "message")
return false;
auto encrypted = stanza.firstChildElement("encrypted");
if (encrypted.isNull())
return false;
qDebug() << "!!!! Got encrypted message!!";
return true;
}
void QXmpp::Omemo::Manager::setClient(QXmppClient *client) {
QXmppClientExtension::setClient(client);
if (!client)
return;
QObject::connect(client, &QXmppClient::connected, this,
&Manager::fetchOwnDevices);
}
void QXmpp::Omemo::Manager::fetchOwnDevices() {
QXmppPubSubIq iq{};
iq.setFrom(client()->configuration().jid());
iq.setTo(client()->configuration().jidBare());
iq.setType(QXmppIq::Get);
iq.setQueryNode(omemoVariant->getDeviceListNode());
client()->sendPacket(iq);
}
QSharedPointer<DeviceService> Manager::getDeviceService() {
return deviceService;
}
void Manager::publishDeviceList(const DeviceList &deviceList) {
// QXmppPubSubIq iq{};
// iq.setFrom(client()->configuration().jid());
// iq.setType(QXmppIq::Set);
// iq.setQueryNode(omemoVariant->getDeviceListNode());
// iq.setItems()
auto iq = omemoVariant->deviceListSetIq(deviceList);
iq.setFrom(client()->configuration().jid());
QString str{};
QXmlStreamWriter info(&str);
iq.toXml(&info);
qInfo() << str;
client()->sendPacket(iq);
}
void Manager::generateDeviceListForSelf() {
qInfo() << "Generate device for self...";
generateDeviceForSelfIfNeeded(DeviceList());
}
void Manager::publishBundle(int deviceId, const Bundle &bundle) {
auto iq = omemoVariant->bundleSetIq(deviceId, bundle);
iq.setFrom(client()->configuration().jid());
QString str{};
QXmlStreamWriter info(&str);
iq.toXml(&info);
qInfo() << str;
client()->sendPacket(iq);
}
void Manager::generateDeviceForSelfIfNeeded(const DeviceList &currentList) {
auto db = deviceService->getDatabase(client()->configuration().jidBare());
auto activeId = db->loadActiveDeviceId();
if (activeId.has_value()) {
qInfo() << "Current device:" << *activeId;
bool found = false;
for (const auto &d : currentList.devices)
if (d.id == *activeId)
found = true;
if (found)
return;
qInfo() << "Could not find device" << *activeId << ", generating new one";
}
qInfo() << "Generating device";
auto deviceId = QRandomGenerator64::system()->bounded(INT32_MAX);
db->saveActiveDeviceId(deviceId);
Device device{};
device.id = deviceId;
auto updatedList = currentList;
updatedList.devices.push_back(device);
publishDeviceList(updatedList);
auto bundle = generateAndSaveBundle(deviceId);
publishBundle(deviceId, bundle);
}
Bundle Manager::generateAndSaveBundle(int deviceId) {
auto database = deviceService->getDatabase(client()->configuration().jidBare());
Bundle result{};
ec_key_pair *ecKeyPair;
curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &ecKeyPair);
signal_buffer *ecPublic;
ec_public_key_serialize(&ecPublic, ec_key_pair_get_public(ecKeyPair));
signal_buffer *ecSecret;
ec_private_key_serialize(&ecSecret, ec_key_pair_get_private(ecKeyPair));
QByteArray identityKey((const char *) signal_buffer_const_data(ecPublic), (int) signal_buffer_len(ecPublic));
QByteArray identityKeySecret((const char *) signal_buffer_const_data(ecSecret), (int) signal_buffer_len(ecSecret));
database->saveIdentityKey(deviceId, identityKey);
database->saveIdentityKeySecret(deviceId, identityKeySecret);
result.ik = identityKey;
// Generate SPK
ec_key_pair *ecSpk;
curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &ecSpk);
signal_buffer *spkPublic, *spkSecret;
ec_public_key_serialize(&spkPublic, ec_key_pair_get_public(ecSpk));
ec_private_key_serialize(&spkSecret, ec_key_pair_get_private(ecSpk));
// Generate SPKs
signal_buffer *signature;
curve_calculate_signature(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &signature,
ec_key_pair_get_private(ecKeyPair), signal_buffer_const_data(spkPublic),
signal_buffer_len(spkPublic));
SignedPreKey spk{};
spk.id = 1;
spk.key.publicKey = QByteArray((const char *) signal_buffer_const_data(spkPublic), (int) signal_buffer_len(spkPublic));
spk.key.secretKey = QByteArray((const char *) signal_buffer_const_data(spkSecret), (int) signal_buffer_len(spkSecret));
spk.signature = QByteArray((const char *) signal_buffer_const_data(signature), (int) signal_buffer_len(signature));
result.spk = spk.key.publicKey;
result.spks = spk.signature;
result.spkId = 1;
// Generate 100 PK
for (auto i = 1; i <= 100; ++i) {
ec_key_pair *currentPreKey;
curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &currentPreKey);
signal_buffer *pkPublic, *pkSecret;
ec_public_key_serialize(&pkPublic, ec_key_pair_get_public(currentPreKey));
ec_private_key_serialize(&pkSecret, ec_key_pair_get_private(currentPreKey));
KeyPair preKey{};
preKey.publicKey = QByteArray((const char *) signal_buffer_const_data(pkPublic), (int) signal_buffer_len(pkPublic));
preKey.secretKey = QByteArray((const char *) signal_buffer_const_data(pkSecret), (int) signal_buffer_len(pkSecret));
database->savePreKey(deviceId, i, preKey);
PreKey pk{};
pk.data = preKey.publicKey;
pk.id = i;
result.prekeys.append(pk);
signal_buffer_free(pkPublic);
signal_buffer_free(pkSecret);
SIGNAL_UNREF(currentPreKey);
}
signal_buffer_free(signature);
signal_buffer_free(ecPublic);
signal_buffer_free(ecSecret);
SIGNAL_UNREF(ecKeyPair);
signal_buffer_free(spkPublic);
signal_buffer_free(spkSecret);
SIGNAL_UNREF(ecSpk);
return result;
}

View File

@ -0,0 +1,64 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include "device_service.h"
#include "qomemo.h"
#include "variant/omemo_base.h"
#include <memory>
#include <QXmppClientExtension.h>
namespace Signal {
class Context;
}
namespace QXmpp::Omemo {
class Manager : public QXmppClientExtension {
Q_OBJECT;
public:
Manager();
~Manager() override = default;
bool handleStanza(const QDomElement &stanza) override;
bool handleDeviceList(const QDomElement& stanza);
bool handleMissingDeviceList(const QDomElement& stanza);
bool handleEncryptedMessage(const QDomElement& stanza);
QSharedPointer<DeviceService> getDeviceService();
Bundle generateAndSaveBundle(int deviceId);
public slots:
void fetchOwnDevices();
void publishDeviceList(const QXmpp::Omemo::DeviceList& deviceList);
void generateDeviceListForSelf();
void generateDeviceForSelfIfNeeded(const QXmpp::Omemo::DeviceList &currentList);
void publishBundle(int deviceId, const QXmpp::Omemo::Bundle& bundle);
signals:
void deviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list);
void deviceListNotFound(const QString &jid);
protected:
void setClient(QXmppClient *client) override;
private:
QSharedPointer<DeviceService> deviceService;
QScopedPointer<Variant::Base> omemoVariant;
std::shared_ptr<Signal::Context> signalContext;
};
} // namespace QXmpp::Omemo

26
qomemo/sce.cpp Normal file
View File

@ -0,0 +1,26 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "sce.h"
#include <QRandomGenerator>
#define RPAD_ALPHABET "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
constexpr int RPAD_MAX_LENGTH = 200;
QString QXmpp::Sce::generatePadding() {
QRandomGenerator random{};
QString result{};
QString alphabet{QStringLiteral(RPAD_ALPHABET)};
auto length = random.bounded(RPAD_MAX_LENGTH);
result.resize(length);
for (auto i = 0; i < length; ++i) {
result[i] = alphabet[random.bounded(alphabet.length())];
}
return result;
}

14
qomemo/sce.h Normal file
View File

@ -0,0 +1,14 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QDateTime>
#include <QXmppElement.h>
namespace QXmpp::Sce {
QString generatePadding();
}

View File

@ -0,0 +1,9 @@
target_sources(squawk PRIVATE
context.cpp
context.h
util.cpp
util.h
)
add_subdirectory(crypto)
add_subdirectory(stores)

28
qomemo/signal/context.cpp Normal file
View File

@ -0,0 +1,28 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "context.h"
#include "crypto/crypto.h"
using namespace Signal;
Context::Context() : cryptoProvider{ Signal::Crypto::createProvider() } {
signal_context_create(&ctx, nullptr);
signal_context_set_crypto_provider(ctx, &cryptoProvider);
}
Context::~Context() {
signal_context_destroy(ctx);
}
std::unique_ptr<Crypto::ECKeyPair> Context::generateCurveKeyPair() {
auto result = std::unique_ptr<Crypto::ECKeyPair>();
// TODO
return result;
}
signal_context *Context::temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped() {
return ctx;
}

32
qomemo/signal/context.h Normal file
View File

@ -0,0 +1,32 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include "crypto/ec.h"
#include <memory>
#include <signal/signal_protocol.h>
namespace Signal {
class Context {
public:
Context();
~Context();
Context(const Context &) = delete;
Context(Context &&) = delete;
Context &operator=(const Context &) = delete;
std::unique_ptr<Crypto::ECKeyPair> generateCurveKeyPair();
signal_context *temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped();
private:
signal_crypto_provider cryptoProvider{};
signal_context *ctx{nullptr};
};
} // namespace Signal

View File

@ -0,0 +1,12 @@
target_sources(squawk PRIVATE
aes_openssl.cpp
aes_openssl.h
crypto.cpp
crypto.h
hmac_sha256_openssl.cpp
hmac_sha256_openssl.h
sha512_digest_openssl.cpp
sha512_digest_openssl.h
ec.cpp
ec.h
)

View File

@ -0,0 +1,187 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "aes_openssl.h"
#include <memory>
extern "C" {
#include <openssl/evp.h>
#include <openssl/opensslv.h>
}
using namespace Signal::Crypto;
class EVPCipherCtxWrapper {
public:
EVPCipherCtxWrapper() {
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
ctx = EVP_CIPHER_CTX_new();
#else
ctx = new EVP_CIPHER_CTX;
EVP_CIPHER_CTX_init(ctx);
#endif
}
~EVPCipherCtxWrapper() {
if (good()) {
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
EVP_CIPHER_CTX_free(ctx);
#else
EVP_CIPHER_CTX_cleanup(ctx);
delete ctx;
#endif
}
}
EVPCipherCtxWrapper(const EVPCipherCtxWrapper &) = delete;
EVPCipherCtxWrapper(EVPCipherCtxWrapper &&) = delete;
EVPCipherCtxWrapper &operator=(const EVPCipherCtxWrapper &) = delete;
[[nodiscard]] bool good() const { return ctx != nullptr; }
[[nodiscard]] EVP_CIPHER_CTX *operator*() const { return ctx; }
private:
EVP_CIPHER_CTX *ctx{nullptr};
};
static const EVP_CIPHER *aes_cipher(int cipher, size_t key_len) {
if (cipher == SG_CIPHER_AES_CBC_PKCS5) {
if (key_len == 16) {
return EVP_aes_128_cbc();
} else if (key_len == 24) {
return EVP_aes_192_cbc();
} else if (key_len == 32) {
return EVP_aes_256_cbc();
}
} else if (cipher == SG_CIPHER_AES_CTR_NOPADDING) {
if (key_len == 16) {
return EVP_aes_128_ctr();
} else if (key_len == 24) {
return EVP_aes_192_ctr();
} else if (key_len == 32) {
return EVP_aes_256_ctr();
}
}
return nullptr;
}
int Aes::encrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv,
size_t iv_len, const uint8_t *plaintext, size_t plaintext_len, void *) {
const EVP_CIPHER *evp_cipher = aes_cipher(cipher, key_len);
if (!evp_cipher) {
fprintf(stderr, "invalid AES mode or key size: %zu\n", key_len);
return SG_ERR_UNKNOWN;
}
if (iv_len != 16) {
fprintf(stderr, "invalid AES IV size: %zu\n", iv_len);
return SG_ERR_UNKNOWN;
}
if (plaintext_len > INT_MAX - EVP_CIPHER_block_size(evp_cipher)) {
fprintf(stderr, "invalid plaintext length: %zu\n", plaintext_len);
return SG_ERR_UNKNOWN;
}
EVPCipherCtxWrapper ctx{};
if (!ctx.good()) {
fprintf(stderr, "could not create context\n");
return SG_ERR_UNKNOWN;
}
auto result = EVP_EncryptInit_ex(*ctx, evp_cipher, nullptr, key, iv);
if (!result) {
fprintf(stderr, "cannot initialize cipher\n");
return SG_ERR_UNKNOWN;
}
if (cipher == SG_CIPHER_AES_CTR_NOPADDING) {
result = EVP_CIPHER_CTX_set_padding(*ctx, 0);
if (!result) {
fprintf(stderr, "cannot set padding\n");
return SG_ERR_UNKNOWN;
}
}
auto out_buf = std::make_unique<uint8_t>(plaintext_len + EVP_CIPHER_block_size(evp_cipher));
int out_len = 0;
result = EVP_EncryptUpdate(*ctx, out_buf.get(), &out_len, plaintext,
plaintext_len);
if (!result) {
fprintf(stderr, "cannot encrypt plaintext\n");
return SG_ERR_UNKNOWN;
}
int final_len = 0;
result = EVP_EncryptFinal_ex(*ctx, out_buf.get() + out_len, &final_len);
if (!result) {
fprintf(stderr, "cannot finish encrypting plaintext\n");
return SG_ERR_UNKNOWN;
}
*output = signal_buffer_create(out_buf.get(), out_len + final_len);
return result;
}
int Aes::decrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv,
size_t iv_len, const uint8_t *ciphertext, size_t ciphertext_len, void *) {
const EVP_CIPHER *evp_cipher = aes_cipher(cipher, key_len);
if (!evp_cipher) {
fprintf(stderr, "invalid AES mode or key size: %zu\n", key_len);
return SG_ERR_INVAL;
}
if (iv_len != 16) {
fprintf(stderr, "invalid AES IV size: %zu\n", iv_len);
return SG_ERR_INVAL;
}
if (ciphertext_len > INT_MAX - EVP_CIPHER_block_size(evp_cipher)) {
fprintf(stderr, "invalid ciphertext length: %zu\n", ciphertext_len);
return SG_ERR_UNKNOWN;
}
EVPCipherCtxWrapper ctx{};
if (!ctx.good()) {
fprintf(stderr, "could not create context\n");
return SG_ERR_UNKNOWN;
}
auto result = EVP_DecryptInit_ex(*ctx, evp_cipher, nullptr, key, iv);
if (!result) {
fprintf(stderr, "cannot initialize cipher\n");
return SG_ERR_UNKNOWN;
}
if (cipher == SG_CIPHER_AES_CTR_NOPADDING) {
result = EVP_CIPHER_CTX_set_padding(*ctx, 0);
if (!result) {
fprintf(stderr, "cannot set padding\n");
return SG_ERR_UNKNOWN;
}
}
auto out_buf = std::make_unique<uint8_t>(ciphertext_len + EVP_CIPHER_block_size(evp_cipher));
int out_len = 0;
result = EVP_DecryptUpdate(*ctx, out_buf.get(), &out_len, ciphertext, ciphertext_len);
if (!result) {
fprintf(stderr, "cannot decrypt ciphertext\n");
return SG_ERR_UNKNOWN;
}
int final_len = 0;
result = EVP_DecryptFinal_ex(*ctx, out_buf.get() + out_len, &final_len);
if (!result) {
fprintf(stderr, "cannot finish decrypting ciphertext\n");
return SG_ERR_UNKNOWN;
}
*output = signal_buffer_create(out_buf.get(), out_len + final_len);
return result;
}

View File

@ -0,0 +1,17 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Crypto::Aes {
int encrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv,
size_t iv_len, const uint8_t *plaintext, size_t plaintext_len, void *user_data);
int decrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv,
size_t iv_len, const uint8_t *ciphertext, size_t ciphertext_len, void *user_data);
} // namespace Signal::Crypto::Aes

View File

@ -0,0 +1,40 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "crypto.h"
extern "C" {
#include <openssl/rand.h>
}
#include "aes_openssl.h"
#include "hmac_sha256_openssl.h"
#include "sha512_digest_openssl.h"
int random_func(uint8_t *data, size_t len, void *) {
if (RAND_bytes(data, len)) {
return 0;
} else {
return SG_ERR_UNKNOWN;
}
}
signal_crypto_provider Signal::Crypto::createProvider() {
signal_crypto_provider result{};
result.random_func = random_func;
result.hmac_sha256_init_func = HmacSha256::init;
result.hmac_sha256_update_func = HmacSha256::update;
result.hmac_sha256_final_func = HmacSha256::final;
result.hmac_sha256_cleanup_func = HmacSha256::cleanup;
result.sha512_digest_init_func = Sha512::init;
result.sha512_digest_update_func = Sha512::update;
result.sha512_digest_final_func = Sha512::final;
result.sha512_digest_cleanup_func = Sha512::cleanup;
result.encrypt_func = Aes::encrypt;
result.decrypt_func = Aes::decrypt;
result.user_data = nullptr;
return result;
}

View File

@ -0,0 +1,13 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Crypto {
signal_crypto_provider createProvider();
}

View File

@ -0,0 +1,5 @@
/*
* Created by victoria on 2021-06-17.
*/
#include "ec.h"

20
qomemo/signal/crypto/ec.h Normal file
View File

@ -0,0 +1,20 @@
/*
* Created by victoria on 2021-06-17.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Crypto {
class ECKeyPair {
public:
ECKeyPair();
~ECKeyPair();
private:
ec_key_pair *ec;
};
}

View File

@ -0,0 +1,71 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "hmac_sha256_openssl.h"
extern "C" {
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>
}
using namespace Signal::Crypto;
int HmacSha256::init(void **hmac_context, const uint8_t *key, size_t key_len, void *) {
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
HMAC_CTX *ctx = HMAC_CTX_new();
if (!ctx) {
return SG_ERR_NOMEM;
}
#else
auto ctx = new HMAC_CTX;
HMAC_CTX_init(ctx);
#endif
*hmac_context = ctx;
if (HMAC_Init_ex(ctx, key, key_len, EVP_sha256(), nullptr) != 1) {
return SG_ERR_UNKNOWN;
}
return SG_SUCCESS;
}
int HmacSha256::update(void *hmac_context, const uint8_t *data, size_t data_len, void *) {
auto ctx = static_cast<HMAC_CTX *>(hmac_context);
int result = HMAC_Update(ctx, data, data_len);
return (result == 1) ? SG_SUCCESS : SG_ERR_UNKNOWN;
}
int HmacSha256::final(void *hmac_context, signal_buffer **output, void *) {
auto ctx = static_cast<HMAC_CTX *>(hmac_context);
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int len = 0;
if (HMAC_Final(ctx, md, &len) != 1) {
return SG_ERR_UNKNOWN;
}
signal_buffer *output_buffer = signal_buffer_create(md, len);
if (!output_buffer) {
return SG_ERR_NOMEM;
}
*output = output_buffer;
return SG_SUCCESS;
}
void HmacSha256::cleanup(void *hmac_context, void *) {
if (hmac_context) {
auto ctx = static_cast<HMAC_CTX *>(hmac_context);
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
HMAC_CTX_free(ctx);
#else
HMAC_CTX_cleanup(ctx);
delete ctx;
#endif
}
}

View File

@ -0,0 +1,16 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Crypto::HmacSha256 {
int init(void **hmac_context, const uint8_t *key, size_t key_len, void *);
int update(void *hmac_context, const uint8_t *data, size_t data_len, void *);
int final(void *hmac_context, signal_buffer **output, void *);
void cleanup(void *hmac_context, void *);
} // namespace Signal::Crypto::HmacSha256

View File

@ -0,0 +1,67 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "sha512_digest_openssl.h"
extern "C" {
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
}
using namespace Signal::Crypto;
int Sha512::init(void **digest_context, void *) {
auto ctx = EVP_MD_CTX_create();
if (!ctx) {
return SG_ERR_NOMEM;
}
auto result = EVP_DigestInit_ex(ctx, EVP_sha512(), nullptr);
if (result == 1) {
*digest_context = ctx;
return SG_SUCCESS;
}
EVP_MD_CTX_destroy(ctx);
return SG_ERR_UNKNOWN;
}
int Sha512::update(void *digest_context, const uint8_t *data, size_t data_len, void *) {
auto ctx = static_cast<EVP_MD_CTX *>(digest_context);
auto result = EVP_DigestUpdate(ctx, data, data_len);
return (result == 1) ? SG_SUCCESS : SG_ERR_UNKNOWN;
}
int Sha512::final(void *digest_context, signal_buffer **output, void *) {
auto ctx = static_cast<EVP_MD_CTX *>(digest_context);
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int len = 0;
auto result = EVP_DigestFinal_ex(ctx, md, &len);
if (result != 1) {
return SG_ERR_UNKNOWN;
}
result = EVP_DigestInit_ex(ctx, EVP_sha512(), nullptr);
if (result != 1) {
return SG_ERR_UNKNOWN;
}
signal_buffer *output_buffer = signal_buffer_create(md, len);
if (!output_buffer) {
return SG_ERR_NOMEM;
}
*output = output_buffer;
return SG_SUCCESS;
}
void Sha512::cleanup(void *digest_context, void *) {
auto ctx = static_cast<EVP_MD_CTX *>(digest_context);
EVP_MD_CTX_destroy(ctx);
}

View File

@ -0,0 +1,16 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Crypto::Sha512 {
int init(void **digest_context, void *);
int update(void *digest_context, const uint8_t *data, size_t data_len, void *);
int final(void *digest_context, signal_buffer **output, void *);
void cleanup(void *digest_context, void *);
} // namespace Signal::Crypto::Sha512

View File

@ -0,0 +1,14 @@
target_sources(squawk PRIVATE
identity_key_store.cpp
identity_key_store.h
pre_key_store.cpp
pre_key_store.h
sender_key_store.cpp
sender_key_store.h
session_store.cpp
session_store.h
signed_pre_key_store.cpp
signed_pre_key_store.h
store_context.cpp
store_context.h
)

View File

@ -0,0 +1,70 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "identity_key_store.h"
#include "qomemo/signal/util.h"
#include <utility>
Signal::Store::IdentityKeyStore::IdentityKeyStore(QXmpp::Omemo::DeviceService &deviceService, QString jid, int deviceId)
: jid(std::move(jid)), deviceId(deviceId), deviceService(deviceService) {
database = deviceService.getDatabase(jid);
}
int Signal::Store::IdentityKeyStore::getIdentityKeyPair(signal_buffer **public_data, signal_buffer **private_data) {
auto pk = database->loadIdentityKey(deviceId);
auto sk = database->loadIdentityKeySecret(deviceId);
*public_data = signal_buffer_create(reinterpret_cast<const uint8_t *>(pk->data()), pk->size());
*private_data = signal_buffer_create(reinterpret_cast<const uint8_t *>(sk->data()), sk->size());
return 0;
}
int Signal::Store::IdentityKeyStore::getLocalRegistrationId(uint32_t *registration_id) {
// TODO: Figure out what registration id is used for
*registration_id = 1;
return 0;
}
int Signal::Store::IdentityKeyStore::saveIdentity(const signal_protocol_address *address, uint8_t *key_data,
size_t key_len) {
auto identityJid = Signal::Util::jidFromAddress(address);
auto identityKey = Signal::Util::byteArray(key_data, key_len);
deviceService.getDatabase(identityJid)->saveIdentityKey(address->device_id, identityKey);
return 0;
}
int Signal::Store::IdentityKeyStore::isTrustedIdentity(const signal_protocol_address *address, uint8_t *key_data,
size_t key_len) {
auto identityJid = Signal::Util::jidFromAddress(address);
auto actualIdentityKey = deviceService.getDatabase(identityJid)->loadIdentityKey(address->device_id);
if (!actualIdentityKey.has_value()) {
return 1;
}
auto givenIdentityKey = Signal::Util::byteArray(key_data, key_len);
return givenIdentityKey == actualIdentityKey ? 1 : 0;
}
void Signal::Store::IdentityKeyStore::fillCallbacks(signal_protocol_identity_key_store &store) {
store.get_identity_key_pair = [](signal_buffer **public_data, signal_buffer **private_data, void *ptr) {
return static_cast<IdentityKeyStore *>(ptr)->getIdentityKeyPair(public_data, private_data);
};
store.get_local_registration_id = [](void *ptr, uint32_t *registrationId) {
return static_cast<IdentityKeyStore *>(ptr)->getLocalRegistrationId(registrationId);
};
store.is_trusted_identity = [](const signal_protocol_address *address, uint8_t *key_data, size_t key_len,
void *ptr) {
return static_cast<IdentityKeyStore *>(ptr)->isTrustedIdentity(address, key_data, key_len);
};
store.save_identity = [](const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *ptr) {
return static_cast<IdentityKeyStore *>(ptr)->saveIdentity(address, key_data, key_len);
};
}

View File

@ -0,0 +1,32 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
#include "qomemo/device_service.h"
namespace Signal::Store {
class IdentityKeyStore {
public:
IdentityKeyStore(QXmpp::Omemo::DeviceService &deviceService, QString jid, int deviceId);
int getIdentityKeyPair(signal_buffer **public_data, signal_buffer **private_data);
int getLocalRegistrationId(uint32_t *registration_id);
int saveIdentity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len);
int isTrustedIdentity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len);
void fillCallbacks(signal_protocol_identity_key_store &store);
const QString jid;
const int deviceId;
private:
QXmpp::Omemo::DeviceService &deviceService;
QSharedPointer<QXmpp::Omemo::Database> database;
};
} // namespace Signal::Store

View File

@ -0,0 +1,38 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "pre_key_store.h"
Signal::Store::PreKeyStore::PreKeyStore(QSharedPointer<QXmpp::Omemo::Database> database) : database(
std::move(database)) {}
int Signal::Store::PreKeyStore::containsPreKey(uint32_t pre_key_id) {
return 0;
}
int Signal::Store::PreKeyStore::loadPreKey(signal_buffer **record, uint32_t pre_key_id) {
return 0;
}
int Signal::Store::PreKeyStore::storePreKey(uint32_t pre_key_id, uint8_t *record, size_t record_len) {
return 0;
}
int Signal::Store::PreKeyStore::removePreKey(uint32_t pre_key_id) { return 0; }
void Signal::Store::PreKeyStore::fillCallbacks(signal_protocol_pre_key_store &store) {
store.contains_pre_key = [](uint32_t id, void *ptr) {
return static_cast<PreKeyStore *>(ptr)->containsPreKey(id);
};
store.load_pre_key = [](signal_buffer **record, uint32_t id, void *ptr) {
return static_cast<PreKeyStore *>(ptr)->loadPreKey(record, id);
};
store.remove_pre_key = [](uint32_t id, void *ptr) {
return static_cast<PreKeyStore *>(ptr)->removePreKey(id);
};
store.store_pre_key = [](uint32_t id, uint8_t *record, size_t size,
void *ptr) {
return static_cast<PreKeyStore *>(ptr)->storePreKey(id, record, size);
};
}

View File

@ -0,0 +1,28 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
#include "qomemo/device_service.h"
namespace Signal::Store {
class PreKeyStore {
public:
explicit PreKeyStore(QSharedPointer<QXmpp::Omemo::Database> database);
int containsPreKey(uint32_t pre_key_id);
int loadPreKey(signal_buffer **record, uint32_t pre_key_id);
int storePreKey(uint32_t pre_key_id, uint8_t *record, size_t record_len);
int removePreKey(uint32_t pre_key_id);
void fillCallbacks(signal_protocol_pre_key_store &store);
private:
QSharedPointer<QXmpp::Omemo::Database> database;
};
} // namespace Signal::Store

View File

@ -0,0 +1,30 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "sender_key_store.h"
int Signal::Store::SenderKeyStore::loadSenderKey(signal_buffer **record, signal_buffer **user_record,
const signal_protocol_sender_key_name *sender_key_name) {
return 0;
}
int Signal::Store::SenderKeyStore::storeSenderKey(const signal_protocol_sender_key_name *sender_key_name,
uint8_t *record, size_t record_len, uint8_t *user_record,
size_t user_record_len) {
return 0;
}
void Signal::Store::SenderKeyStore::fillCallbacks(signal_protocol_sender_key_store &store) {
store.user_data = nullptr;
store.destroy_func = nullptr;
store.load_sender_key = [](signal_buffer **record, signal_buffer **user_record,
const signal_protocol_sender_key_name *sender_key_name, void *ptr) {
return static_cast<SenderKeyStore *>(ptr)->loadSenderKey(record, user_record, sender_key_name);
};
store.store_sender_key = [](const signal_protocol_sender_key_name *sender_key_name, uint8_t *record,
size_t record_len, uint8_t *user_record, size_t user_record_len, void *ptr) {
return static_cast<SenderKeyStore *>(ptr)->storeSenderKey(sender_key_name, record, record_len, user_record,
user_record_len);
};
}

View File

@ -0,0 +1,21 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Store {
class SenderKeyStore {
public:
int storeSenderKey(const signal_protocol_sender_key_name *sender_key_name, uint8_t *record, size_t record_len,
uint8_t *user_record, size_t user_record_len);
int loadSenderKey(signal_buffer **record, signal_buffer **user_record,
const signal_protocol_sender_key_name *sender_key_name);
void fillCallbacks(signal_protocol_sender_key_store &store);
};
} // namespace Signal::Store

View File

@ -0,0 +1,57 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "session_store.h"
int Signal::Store::SessionStore::loadSession(signal_buffer **record, signal_buffer **user_record,
const signal_protocol_address *address) {
return 0;
}
int Signal::Store::SessionStore::getSubDeviceSessions(signal_int_list **sessions, const char *name, size_t name_len) {
return 0;
}
int Signal::Store::SessionStore::storeSession(const signal_protocol_address *address, uint8_t *record,
size_t record_len, uint8_t *user_record, size_t user_record_len) {
return 0;
}
int Signal::Store::SessionStore::containsSession(const signal_protocol_address *address) {
return 0;
}
int Signal::Store::SessionStore::deleteSession(const signal_protocol_address *address) {
return 0;
}
int Signal::Store::SessionStore::deleteAllSessions(const char *name, size_t name_len) {
return 0;
}
void Signal::Store::SessionStore::fillCallbacks(signal_protocol_session_store &store) {
store.user_data = nullptr;
store.destroy_func = nullptr;
store.load_session_func = [](signal_buffer **record, signal_buffer **user_record,
const signal_protocol_address *address, void *ptr) {
return static_cast<SessionStore *>(ptr)->loadSession(record, user_record, address);
};
store.get_sub_device_sessions_func = [](signal_int_list **sessions, const char *name, size_t name_len, void *ptr) {
return static_cast<SessionStore *>(ptr)->getSubDeviceSessions(sessions, name, name_len);
};
store.store_session_func = [](const signal_protocol_address *address, uint8_t *record, size_t record_len,
uint8_t *user_record, size_t user_record_len, void *ptr) {
return static_cast<SessionStore *>(ptr)->storeSession(address, record, record_len, user_record,
user_record_len);
};
store.contains_session_func = [](const signal_protocol_address *address, void *ptr) {
return static_cast<SessionStore *>(ptr)->containsSession(address);
};
store.delete_session_func = [](const signal_protocol_address *address, void *ptr) {
return static_cast<SessionStore *>(ptr)->deleteSession(address);
};
store.delete_all_sessions_func = [](const char *name, size_t name_len, void *ptr) {
return static_cast<SessionStore *>(ptr)->deleteAllSessions(name, name_len);
};
}

View File

@ -0,0 +1,24 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Store {
class SessionStore {
public:
int loadSession(signal_buffer **record, signal_buffer **user_record, const signal_protocol_address *address);
int getSubDeviceSessions(signal_int_list **sessions, const char *name, size_t name_len);
int storeSession(const signal_protocol_address *address, uint8_t *record, size_t record_len,
uint8_t *user_record, size_t user_record_len);
int containsSession(const signal_protocol_address *address);
int deleteSession(const signal_protocol_address *address);
int deleteAllSessions(const char *name, size_t name_len);
void fillCallbacks(signal_protocol_session_store &store);
};
} // namespace Signal::Store

View File

@ -0,0 +1,43 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "signed_pre_key_store.h"
int Signal::Store::SignedPreKeyStore::loadSignedPreKey(signal_buffer **record, uint32_t signed_pre_key_id) {
return 0;
}
int
Signal::Store::SignedPreKeyStore::storeSignedPreKey(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len) {
return 0;
}
int Signal::Store::SignedPreKeyStore::containsSignedPreKey(uint32_t signed_pre_key_id) {
return 0;
}
int Signal::Store::SignedPreKeyStore::removeSignedPreKey(uint32_t signed_pre_key_id) {
return 0;
}
void Signal::Store::SignedPreKeyStore::fillCallbacks(signal_protocol_signed_pre_key_store &store) {
store.user_data = nullptr;
store.destroy_func = nullptr;
store.load_signed_pre_key = [](signal_buffer **record, uint32_t signed_pre_key_id, void *ptr) {
return static_cast<SignedPreKeyStore *>(ptr)->loadSignedPreKey(
record, signed_pre_key_id);
};
store.store_signed_pre_key = [](uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *ptr) {
return static_cast<SignedPreKeyStore *>(ptr)->storeSignedPreKey(
signed_pre_key_id, record, record_len);
};
store.contains_signed_pre_key = [](uint32_t signed_pre_key_id, void *ptr) {
return static_cast<SignedPreKeyStore *>(ptr)->containsSignedPreKey(
signed_pre_key_id);
};
store.remove_signed_pre_key = [](uint32_t signed_pre_key_id, void *ptr) {
return static_cast<SignedPreKeyStore *>(ptr)->removeSignedPreKey(
signed_pre_key_id);
};
}

View File

@ -0,0 +1,21 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Store {
class SignedPreKeyStore {
public:
int loadSignedPreKey(signal_buffer **record, uint32_t signed_pre_key_id);
int storeSignedPreKey(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len);
int containsSignedPreKey(uint32_t signed_pre_key_id);
int removeSignedPreKey(uint32_t signed_pre_key_id);
void fillCallbacks(signal_protocol_signed_pre_key_store &store);
};
} // namespace Signal::Store

View File

@ -0,0 +1,25 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "store_context.h"
Signal::Store::Context::Context(signal_context *global) : identityKeyStore(), preKeyStore(), senderKeyStore(), sessionStore(), signedPreKeyStore() {
signal_protocol_store_context_create(&ctx, global);
identityKeyStore->fillCallbacks(iks);
preKeyStore->fillCallbacks(pks);
senderKeyStore->fillCallbacks(sks);
sessionStore->fillCallbacks(ss);
signedPreKeyStore->fillCallbacks(spks);
signal_protocol_store_context_set_identity_key_store(ctx, &iks);
signal_protocol_store_context_set_pre_key_store(ctx, &pks);
signal_protocol_store_context_set_sender_key_store(ctx, &sks);
signal_protocol_store_context_set_session_store(ctx, &ss);
signal_protocol_store_context_set_signed_pre_key_store(ctx, &spks);
}
Signal::Store::Context::~Context() {
signal_protocol_store_context_destroy(ctx);
}

View File

@ -0,0 +1,42 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
#include "identity_key_store.h"
#include "pre_key_store.h"
#include "sender_key_store.h"
#include "session_store.h"
#include "signed_pre_key_store.h"
namespace Signal::Store {
class Context {
public:
explicit Context(signal_context *global);
~Context();
Context(const Context &) = delete;
Context(Context &&) = delete;
Context &operator=(const Context &) = delete;
private:
signal_protocol_store_context *ctx{nullptr};
signal_protocol_identity_key_store iks{};
signal_protocol_pre_key_store pks{};
signal_protocol_sender_key_store sks{};
signal_protocol_session_store ss{};
signal_protocol_signed_pre_key_store spks{};
std::unique_ptr<IdentityKeyStore> identityKeyStore;
std::unique_ptr<PreKeyStore> preKeyStore;
std::unique_ptr<SenderKeyStore> senderKeyStore;
std::unique_ptr<SessionStore> sessionStore;
std::unique_ptr<SignedPreKeyStore> signedPreKeyStore;
};
} // namespace Signal::Store

14
qomemo/signal/util.cpp Normal file
View File

@ -0,0 +1,14 @@
/*
* Created by victoria on 2021-05-15.
*/
#include "util.h"
QString Signal::Util::jidFromAddress(const signal_protocol_address *address) {
// TODO: Validate this
return QString::fromRawData(reinterpret_cast<const QChar *>(address->name), static_cast<int>(address->name_len));
}
QByteArray Signal::Util::byteArray(const uint8_t *data, size_t len) {
return QByteArray::fromRawData(reinterpret_cast<const char *>(data), static_cast<int>(len));
}

18
qomemo/signal/util.h Normal file
View File

@ -0,0 +1,18 @@
/*
* Created by victoria on 2021-05-15.
*/
#pragma once
#include <signal/signal_protocol.h>
#include <QString>
#include <QByteArray>
namespace Signal::Util {
QString jidFromAddress(const signal_protocol_address *address);
QByteArray byteArray(const uint8_t *data, size_t len);
}

View File

@ -0,0 +1,9 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "user_device_list.h"
#include <utility>
QXmpp::Omemo::UserDeviceList::UserDeviceList(QString jid) : jid(std::move(jid)) {}

18
qomemo/user_device_list.h Normal file
View File

@ -0,0 +1,18 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QString>
namespace QXmpp::Omemo {
class UserDeviceList {
public:
explicit UserDeviceList(QString jid);
const QString jid;
};
} // namespace QXmpp::Omemo

View File

@ -0,0 +1 @@
target_sources(squawk PRIVATE xep0384.cpp xep0384.h conversations.cpp conversations.h omemo_base.cpp omemo_base.h)

View File

@ -0,0 +1,205 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "conversations.h"
#include "qomemo/bundle.h"
#include "qomemo/device.h"
#include "shared/qxmppfactories.h"
#include <QDebug>
#include <QXmppIq.h>
#include <QXmppPubSubIq.h>
using namespace QXmpp::Omemo;
using namespace QXmpp::Factories;
QXmppElement Variant::Conversations::deviceToXml(const Device &device) {
auto result = createElement("device");
result.setAttribute("id", QString::number(device.id));
return result;
}
Device Variant::Conversations::deviceFromXml(const QXmppElement &xml) {
Device result{};
if (!elementMatches(xml, "device"))
return result;
result.id = xml.attribute("id").toInt();
return result;
}
QXmppElement
Variant::Conversations::deviceListToXml(const DeviceList &deviceList) {
auto element = createElement("list", "eu.siacs.conversations.axolotl");
for (const auto &device : deviceList.devices) {
element.appendChild(deviceToXml(device));
}
return element;
}
std::optional<DeviceList> Variant::Conversations::latestDeviceListFromPubSubNode(const QXmppElement &items) {
auto item = items.firstChildElement("item");
if (item.isNull())
return std::nullopt;
auto list = item.firstChildElement("list");
if (list.isNull())
return std::nullopt;
if (!elementMatches(list, "list", "eu.siacs.conversations.axolotl"))
return std::nullopt;
DeviceList result{};
auto deviceElement = list.firstChildElement("device");
while (!deviceElement.isNull()) {
result.devices.push_back(deviceFromXml(deviceElement));
deviceElement = deviceElement.nextSiblingElement("device");
}
return result;
}
QXmppIq Variant::Conversations::deviceListSetIq(const DeviceList &deviceList) {
QXmppIq iq{};
iq.setType(QXmppIq::Set);
auto item = createElement("item");
item.appendChild(deviceListToXml(deviceList));
auto publish = createElement("publish");
publish.setAttribute("node", getDeviceListNode());
publish.appendChild(item);
auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
pubSub.appendChild(publish);
pubSub.appendChild(createOpenPublishOptions());
iq.setExtensions({ pubSub });
return iq;
}
QString Variant::Conversations::getDeviceListNode() const {
return "eu.siacs.conversations.axolotl.devicelist";
}
QXmppIq Variant::Conversations::bundleSetIq(int deviceId, const Bundle &bundle) {
QXmppIq iq{};
iq.setType(QXmppIq::Set);
auto item = createElement("item");
item.appendChild(bundleToXml(bundle));
auto publish = createElement("publish");
publish.setAttribute(
"node",
QString("eu.siacs.conversations.axolotl.bundles:%1").arg(deviceId));
publish.appendChild(item);
auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
pubSub.appendChild(publish);
pubSub.appendChild(createOpenPublishOptions());
iq.setExtensions({ pubSub });
return iq;
}
QXmppElement Variant::Conversations::bundleToXml(const Bundle &bundle) {
auto spkNode = createElement("signedPreKeyPublic");
spkNode.setAttribute("signedPreKeyId", QString::number(bundle.spkId));
spkNode.setValue(bundle.spk.toBase64());
auto spksNode = createElement("signedPreKeySignature");
spksNode.setValue(bundle.spks.toBase64());
auto ikNode = createElement("identityKey");
ikNode.setValue(bundle.ik.toBase64());
auto prekeysNode = createElement("prekeys");
for (const auto &pk : bundle.prekeys) {
prekeysNode.appendChild(preKeyToXml(pk));
}
auto result = createElement("bundle", "eu.siacs.conversations.axolotl");
result.appendChild(spkNode);
result.appendChild(spksNode);
result.appendChild(ikNode);
result.appendChild(prekeysNode);
return result;
}
QXmppElement Variant::Conversations::preKeyToXml(const PreKey &pk) {
auto x = createElement("preKeyPublic");
x.setAttribute("preKeyId", QString::number(pk.id));
x.setValue(pk.data.toBase64());
return x;
}
std::optional<PreKey> Variant::Conversations::preKeyFromXml(const QXmppElement &xml) {
if (!elementMatches(xml, "preKeyPublic"))
return std::nullopt;
auto pk = PreKey();
pk.id = xml.attribute("preKeyId").toInt();
pk.data = QByteArray::fromBase64Encoding(xml.value().toUtf8()).decoded;
return pk;
}
std::optional<Bundle> Variant::Conversations::bundleFromXml(const QXmppElement &xml) {
if (!elementMatches(xml, "bundle", "eu.siacs.conversations.axolotl"))
return std::nullopt;
auto spkNode = xml.firstChildElement("signedPreKeyPublic");
if (spkNode.isNull()) {
qWarning() << "'bundle': missing 'signedPreKeyPublic'";
return std::nullopt;
}
Bundle result{};
result.spk = QByteArray::fromBase64Encoding(spkNode.value().toUtf8()).decoded;
result.spkId = spkNode.attribute("signedPreKeyId").toInt();
auto spksNode = xml.firstChildElement("signedPreKeySignature");
if (spksNode.isNull()) {
qWarning() << "'bundle': missing 'signedPreKeySignature'";
return std::nullopt;
}
result.spks = QByteArray::fromBase64Encoding(spksNode.value().toUtf8()).decoded;
auto ikNode = xml.firstChildElement("identityKey");
if (ikNode.isNull()) {
qWarning() << "'bundle': missing 'identityKey'";
return std::nullopt;
}
result.ik = QByteArray::fromBase64Encoding(ikNode.value().toUtf8()).decoded;
auto prekeysNode = xml.firstChildElement("prekeys");
auto pkNode = prekeysNode.firstChildElement("preKeyPublic");
result.prekeys.clear();
while (!pkNode.isNull()) {
auto maybePk = preKeyFromXml(pkNode);
if (maybePk)
result.prekeys.push_back(*maybePk);
pkNode = pkNode.nextSiblingElement("preKeyPublic");
}
return result;
}

View File

@ -0,0 +1,32 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include "omemo_base.h"
namespace QXmpp::Omemo::Variant {
class Conversations : public Base {
public:
~Conversations() override = default;
QXmppElement deviceToXml(const Device &device) override;
Device deviceFromXml(const QXmppElement &xml) override;
[[nodiscard]] QString getDeviceListNode() const override;
QXmppElement deviceListToXml(const DeviceList &deviceList) override;
std::optional<DeviceList> latestDeviceListFromPubSubNode(const QXmppElement &items) override;
QXmppIq deviceListSetIq(const DeviceList &deviceList) override;
QXmppElement preKeyToXml(const PreKey &pk) override;
std::optional<PreKey> preKeyFromXml(const QXmppElement &xml) override;
QXmppElement bundleToXml(const Bundle &bundle) override;
std::optional<Bundle> bundleFromXml(const QXmppElement &xml) override;
QXmppIq bundleSetIq(int deviceId, const Bundle &bundle) override;
};
} // namespace QXmpp::Omemo::Variant

View File

@ -0,0 +1,5 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "omemo_base.h"

View File

@ -0,0 +1,45 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <optional>
class QString;
class QXmppElement;
class QXmppIq;
namespace QXmpp::Omemo {
class PreKey;
class Bundle;
class Device;
class DeviceList;
namespace Variant {
class Base {
public:
virtual ~Base() = default;
virtual QXmppElement deviceToXml(const Device &device) = 0;
virtual Device deviceFromXml(const QXmppElement &xml) = 0;
[[nodiscard]] virtual QString getDeviceListNode() const = 0;
virtual QXmppElement deviceListToXml(const DeviceList &deviceList) = 0;
virtual std::optional<DeviceList> latestDeviceListFromPubSubNode(const QXmppElement &xml) = 0;
virtual QXmppIq deviceListSetIq(const DeviceList &deviceList) = 0;
virtual QXmppElement preKeyToXml(const PreKey &pk) = 0;
virtual std::optional<PreKey> preKeyFromXml(const QXmppElement &xml) = 0;
virtual QXmppElement bundleToXml(const Bundle& bundle) = 0;
virtual std::optional<Bundle> bundleFromXml(const QXmppElement& xml) = 0;
virtual QXmppIq bundleSetIq(int deviceId, const Bundle& bundle) = 0;
};
} // namespace Variant
} // namespace QXmpp::Omemo

View File

@ -0,0 +1,5 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "xep0384.h"

7
qomemo/variant/xep0384.h Normal file
View File

@ -0,0 +1,7 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
namespace QXmpp::Omemo {}

14
resources/CMakeLists.txt Normal file
View File

@ -0,0 +1,14 @@
target_sources(squawk PRIVATE resources.qrc)
configure_file(images/logo.svg squawk.svg COPYONLY)
execute_process(COMMAND convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)

View File

@ -0,0 +1,11 @@
<!DOCTYPE svg>
<svg viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 4 3 L 4 19 L 15 19 L 15 18 L 5 18 L 5 4 L 13 4 L 13 8 L 17 8 L 17 16 L 18 16 L 18 7 L 14 3 L 4 3 Z M 13 11 C 11.338 11 10 12.338 10 14 C 10 15.662 11.338 17 13 17 C 13.6494 17 14.2464 16.7914 14.7363 16.4434 L 17.293 19 L 18 18.293 L 15.4434 15.7363 C 15.7914 15.2464 16 14.6494 16 14 C 16 12.338 14.662 11 13 11 Z M 13 12 C 14.108 12 15 12.892 15 14 C 15 15.108 14.108 16 13 16 C 11.892 16 11 15.108 11 14 C 11 12.892 11.892 12 13 12 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 807 B

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="32" version="1.1" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 32 32" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
<defs id="defs5455">
<linearGradient inkscape:collect="always" id="linearGradient4172-5">
<stop style="stop-color:#3daee9" id="stop4174-6"/>
<stop offset="1" style="stop-color:#6cc1ef" id="stop4176-6"/>
</linearGradient>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4172-5" id="linearGradient4342" y1="29" y2="8" x2="0" gradientUnits="userSpaceOnUse"/>
</defs>
<metadata id="metadata5458"/>
<g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -384.57143 -515.798)">
<path inkscape:connector-curvature="0" style="fill:#147eb8;fill-rule:evenodd" id="path4308" d="m 386.57144,518.798 0,7 0,1 28,0 0,-6 -14.00001,0 -2,-2 z"/>
<path inkscape:connector-curvature="0" style="fill-opacity:0.235294;fill-rule:evenodd" id="path4306" d="m 397.57143,523.798 -1.99999,1 -9,0 0,1 6.99999,0 3,0 z"/>
<path style="fill:url(#linearGradient4342)" id="rect4294" d="M 13 8 L 11 10 L 2 10 L 1 10 L 1 29 L 12 29 L 13 29 L 31 29 L 31 8 L 13 8 z " transform="matrix(1 0 0 1 384.57143 515.798)"/>
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4304" d="m 397.57143,523.798 -2,2 -10,0 0,1 11,0 z"/>
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4310" d="m 398.57143,518.798 1,3 15.00001,0 0,-1 -14.00001,0 z"/>
<rect width="30" x="385.57144" y="543.79797" height="1" style="fill-opacity:0.235294" id="rect4292"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,12 @@
<!DOCTYPE svg>
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 3 2 L 3 14 L 10 14 L 10 13 L 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 11 L 13 11 L 13 5 L 10 2 L 3 2 Z"/>
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 8.48828 7 C 7.10757 7 5.98828 8.11929 5.98828 9.5 C 5.98828 10.8807 7.10757 12 8.48828 12 C 8.97811 11.9992 9.45691 11.8546 9.86523 11.584 L 12.2813 14 L 12.9883 13.293 L 10.5723 10.877 C 10.8428 10.4686 10.9875 9.98983 10.9883 9.5 C 10.9883 8.11929 9.86899 7 8.48828 7 Z M 8.48828 8 C 9.31671 8 9.98828 8.67157 9.98828 9.5 C 9.98828 10.3284 9.31671 11 8.48828 11 C 7.65985 11 6.98828 10.3284 6.98828 9.5 C 6.98828 8.67157 7.65985 8 8.48828 8 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1010 B

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 2 2 L 2 3 L 2 6 L 2 7 L 2 13 L 2 14 L 14 14 L 14 13 L 14 6 L 14 5 L 14 4 L 9.0078125 4 L 7.0078125 2 L 7 2.0078125 L 7 2 L 3 2 L 2 2 z M 3 3 L 6.5917969 3 L 7.59375 4 L 7 4 L 7 4.0078125 L 6.9921875 4 L 4.9921875 6 L 3 6 L 3 3 z M 3 7 L 13 7 L 13 13 L 3 13 L 3 7 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 609 B

View File

@ -0,0 +1,11 @@
<!DOCTYPE svg>
<svg viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 4 3 L 4 19 L 15 19 L 15 18 L 5 18 L 5 4 L 13 4 L 13 8 L 17 8 L 17 16 L 18 16 L 18 7 L 14 3 L 4 3 Z M 13 11 C 11.338 11 10 12.338 10 14 C 10 15.662 11.338 17 13 17 C 13.6494 17 14.2464 16.7914 14.7363 16.4434 L 17.293 19 L 18 18.293 L 15.4434 15.7363 C 15.7914 15.2464 16 14.6494 16 14 C 16 12.338 14.662 11 13 11 Z M 13 12 C 14.108 12 15 12.892 15 14 C 15 15.108 14.108 16 13 16 C 11.892 16 11 15.108 11 14 C 11 12.892 11.892 12 13 12 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 807 B

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="32" version="1.1" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 32 32" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
<defs id="defs5455">
<linearGradient inkscape:collect="always" id="linearGradient4172-5">
<stop style="stop-color:#3daee9" id="stop4174-6"/>
<stop offset="1" style="stop-color:#6cc1ef" id="stop4176-6"/>
</linearGradient>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4172-5" id="linearGradient4342" y1="29" y2="8" x2="0" gradientUnits="userSpaceOnUse"/>
</defs>
<metadata id="metadata5458"/>
<g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -384.57143 -515.798)">
<path inkscape:connector-curvature="0" style="fill:#147eb8;fill-rule:evenodd" id="path4308" d="m 386.57144,518.798 0,7 0,1 28,0 0,-6 -14.00001,0 -2,-2 z"/>
<path inkscape:connector-curvature="0" style="fill-opacity:0.235294;fill-rule:evenodd" id="path4306" d="m 397.57143,523.798 -1.99999,1 -9,0 0,1 6.99999,0 3,0 z"/>
<path style="fill:url(#linearGradient4342)" id="rect4294" d="M 13 8 L 11 10 L 2 10 L 1 10 L 1 29 L 12 29 L 13 29 L 31 29 L 31 8 L 13 8 z " transform="matrix(1 0 0 1 384.57143 515.798)"/>
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4304" d="m 397.57143,523.798 -2,2 -10,0 0,1 11,0 z"/>
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4310" d="m 398.57143,518.798 1,3 15.00001,0 0,-1 -14.00001,0 z"/>
<rect width="30" x="385.57144" y="543.79797" height="1" style="fill-opacity:0.235294" id="rect4292"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,12 @@
<!DOCTYPE svg>
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 3 2 L 3 14 L 10 14 L 10 13 L 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 11 L 13 11 L 13 5 L 10 2 L 3 2 Z"/>
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 8.48828 7 C 7.10757 7 5.98828 8.11929 5.98828 9.5 C 5.98828 10.8807 7.10757 12 8.48828 12 C 8.97811 11.9992 9.45691 11.8546 9.86523 11.584 L 12.2813 14 L 12.9883 13.293 L 10.5723 10.877 C 10.8428 10.4686 10.9875 9.98983 10.9883 9.5 C 10.9883 8.11929 9.86899 7 8.48828 7 Z M 8.48828 8 C 9.31671 8 9.98828 8.67157 9.98828 9.5 C 9.98828 10.3284 9.31671 11 8.48828 11 C 7.65985 11 6.98828 10.3284 6.98828 9.5 C 6.98828 8.67157 7.65985 8 8.48828 8 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1010 B

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 2 2 L 2 3 L 2 6 L 2 7 L 2 13 L 2 14 L 14 14 L 14 13 L 14 6 L 14 5 L 14 4 L 9.0078125 4 L 7.0078125 2 L 7 2.0078125 L 7 2 L 3 2 L 2 2 z M 3 3 L 6.5917969 3 L 7.59375 4 L 7 4 L 7 4.0078125 L 6.9921875 4 L 4.9921875 6 L 3 6 L 3 3 z M 3 7 L 13 7 L 13 13 L 3 13 L 3 7 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 609 B

Some files were not shown because too many files have changed in this diff Show More